""" Template for each `dtype` helper function for sparse ops WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in """ # ---------------------------------------------------------------------- # Sparse op # ---------------------------------------------------------------------- ctypedef fused sparse_t: float64_t int64_t cdef float64_t __div__(sparse_t a, sparse_t b): if b == 0: if a > 0: return INF elif a < 0: return -INF else: return NaN else: return float(a) / b cdef float64_t __truediv__(sparse_t a, sparse_t b): return __div__(a, b) cdef sparse_t __mod__(sparse_t a, sparse_t b): if b == 0: if sparse_t is float64_t: return NaN else: return 0 else: return a % b cdef sparse_t __floordiv__(sparse_t a, sparse_t b): if b == 0: if sparse_t is float64_t: # Match non-sparse Series behavior implemented in mask_zero_div_zero if a > 0: return INF elif a < 0: return -INF return NaN else: return 0 else: return a // b # ---------------------------------------------------------------------- # sparse array op # ---------------------------------------------------------------------- {{py: # dtype, arith_comp_group, logical_group dtypes = [('float64', True, False), ('int64', True, True), ('uint8', False, True)] # do not generate arithmetic / comparison template for uint8, # it should be done in fused types def get_op(tup): assert isinstance(tup, tuple) assert len(tup) == 4 opname, lval, rval, dtype = tup ops_dict = {'add': '{0} + {1}', 'sub': '{0} - {1}', 'mul': '{0} * {1}', 'div': '__div__({0}, {1})', 'mod': '__mod__({0}, {1})', 'truediv': '__truediv__({0}, {1})', 'floordiv': '__floordiv__({0}, {1})', 'pow': '{0} ** {1}', 'eq': '{0} == {1}', 'ne': '{0} != {1}', 'lt': '{0} < {1}', 'gt': '{0} > {1}', 'le': '{0} <= {1}', 'ge': '{0} >= {1}', 'and': '{0} & {1}', # logical op 'or': '{0} | {1}', 'xor': '{0} ^ {1}'} return ops_dict[opname].format(lval, rval) def get_dispatch(dtypes): ops_list = ['add', 'sub', 'mul', 'div', 'mod', 'truediv', 'floordiv', 'pow', 'eq', 'ne', 'lt', 'gt', 'le', 'ge', 'and', 'or', 'xor'] for opname in ops_list: for dtype, arith_comp_group, logical_group in dtypes: if opname in ('div', 'truediv'): rdtype = 'float64' elif opname in ('eq', 'ne', 'lt', 'gt', 'le', 'ge'): # comparison op rdtype = 'uint8' elif opname in ('and', 'or', 'xor'): # logical op rdtype = 'uint8' else: rdtype = dtype if opname in ('and', 'or', 'xor'): if logical_group: yield opname, dtype, rdtype else: if arith_comp_group: yield opname, dtype, rdtype }} {{for opname, dtype, rdtype in get_dispatch(dtypes)}} {{if opname == "pow"}} @cython.cpow(True) # Cython 3 matches Python pow, which isn't what we want here {{endif}} @cython.wraparound(False) @cython.boundscheck(False) cdef tuple block_op_{{opname}}_{{dtype}}({{dtype}}_t[:] x_, BlockIndex xindex, {{dtype}}_t xfill, {{dtype}}_t[:] y_, BlockIndex yindex, {{dtype}}_t yfill): """ Binary operator on BlockIndex objects with fill values """ cdef: BlockIndex out_index Py_ssize_t xi = 0, yi = 0, out_i = 0 # fp buf indices int32_t xbp = 0, ybp = 0 # block positions int32_t xloc, yloc Py_ssize_t xblock = 0, yblock = 0 # block numbers {{dtype}}_t[:] x, y ndarray[{{rdtype}}_t, ndim=1] out # to suppress Cython warning x = x_ y = y_ out_index = xindex.make_union(yindex) out = np.empty(out_index.npoints, dtype=np.{{rdtype}}) # Wow, what a hack job. Need to do something about this # walk the two SparseVectors, adding matched locations... for out_i in range(out_index.npoints): if yblock == yindex.nblocks: # use y fill value out[out_i] = {{(opname, 'x[xi]', 'yfill', dtype) | get_op}} xi += 1 # advance x location xbp += 1 if xbp == xindex.lenbuf[xblock]: xblock += 1 xbp = 0 continue if xblock == xindex.nblocks: # use x fill value out[out_i] = {{(opname, 'xfill', 'y[yi]', dtype) | get_op}} yi += 1 # advance y location ybp += 1 if ybp == yindex.lenbuf[yblock]: yblock += 1 ybp = 0 continue yloc = yindex.locbuf[yblock] + ybp xloc = xindex.locbuf[xblock] + xbp # each index in the out_index had to come from either x, y, or both if xloc == yloc: out[out_i] = {{(opname, 'x[xi]', 'y[yi]', dtype) | get_op}} xi += 1 yi += 1 # advance both locations xbp += 1 if xbp == xindex.lenbuf[xblock]: xblock += 1 xbp = 0 ybp += 1 if ybp == yindex.lenbuf[yblock]: yblock += 1 ybp = 0 elif xloc < yloc: # use y fill value out[out_i] = {{(opname, 'x[xi]', 'yfill', dtype) | get_op}} xi += 1 # advance x location xbp += 1 if xbp == xindex.lenbuf[xblock]: xblock += 1 xbp = 0 else: # use x fill value out[out_i] = {{(opname, 'xfill', 'y[yi]', dtype) | get_op}} yi += 1 # advance y location ybp += 1 if ybp == yindex.lenbuf[yblock]: yblock += 1 ybp = 0 return out, out_index, {{(opname, 'xfill', 'yfill', dtype) | get_op}} {{if opname == "pow"}} @cython.cpow(True) # Cython 3 matches Python pow, which isn't what we want here {{endif}} @cython.wraparound(False) @cython.boundscheck(False) cdef tuple int_op_{{opname}}_{{dtype}}({{dtype}}_t[:] x_, IntIndex xindex, {{dtype}}_t xfill, {{dtype}}_t[:] y_, IntIndex yindex, {{dtype}}_t yfill): cdef: IntIndex out_index Py_ssize_t xi = 0, yi = 0, out_i = 0 # fp buf indices int32_t xloc, yloc int32_t[:] xindices, yindices, out_indices {{dtype}}_t[:] x, y ndarray[{{rdtype}}_t, ndim=1] out # suppress Cython compiler warnings due to inlining x = x_ y = y_ # need to do this first to know size of result array out_index = xindex.make_union(yindex) out = np.empty(out_index.npoints, dtype=np.{{rdtype}}) xindices = xindex.indices yindices = yindex.indices out_indices = out_index.indices # walk the two SparseVectors, adding matched locations... for out_i in range(out_index.npoints): if xi == xindex.npoints: # use x fill value out[out_i] = {{(opname, 'xfill', 'y[yi]', dtype) | get_op}} yi += 1 continue if yi == yindex.npoints: # use y fill value out[out_i] = {{(opname, 'x[xi]', 'yfill', dtype) | get_op}} xi += 1 continue xloc = xindices[xi] yloc = yindices[yi] # each index in the out_index had to come from either x, y, or both if xloc == yloc: out[out_i] = {{(opname, 'x[xi]', 'y[yi]', dtype) | get_op}} xi += 1 yi += 1 elif xloc < yloc: # use y fill value out[out_i] = {{(opname, 'x[xi]', 'yfill', dtype) | get_op}} xi += 1 else: # use x fill value out[out_i] = {{(opname, 'xfill', 'y[yi]', dtype) | get_op}} yi += 1 return out, out_index, {{(opname, 'xfill', 'yfill', dtype) | get_op}} cpdef sparse_{{opname}}_{{dtype}}({{dtype}}_t[:] x, SparseIndex xindex, {{dtype}}_t xfill, {{dtype}}_t[:] y, SparseIndex yindex, {{dtype}}_t yfill): if isinstance(xindex, BlockIndex): return block_op_{{opname}}_{{dtype}}(x, xindex.to_block_index(), xfill, y, yindex.to_block_index(), yfill) elif isinstance(xindex, IntIndex): return int_op_{{opname}}_{{dtype}}(x, xindex.to_int_index(), xfill, y, yindex.to_int_index(), yfill) else: raise NotImplementedError {{endfor}}