sparse_op_helper.pxi.in 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. """
  2. Template for each `dtype` helper function for sparse ops
  3. WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in
  4. """
  5. # ----------------------------------------------------------------------
  6. # Sparse op
  7. # ----------------------------------------------------------------------
  8. ctypedef fused sparse_t:
  9. float64_t
  10. int64_t
  11. cdef float64_t __div__(sparse_t a, sparse_t b):
  12. if b == 0:
  13. if a > 0:
  14. return INF
  15. elif a < 0:
  16. return -INF
  17. else:
  18. return NaN
  19. else:
  20. return float(a) / b
  21. cdef float64_t __truediv__(sparse_t a, sparse_t b):
  22. return __div__(a, b)
  23. cdef sparse_t __mod__(sparse_t a, sparse_t b):
  24. if b == 0:
  25. if sparse_t is float64_t:
  26. return NaN
  27. else:
  28. return 0
  29. else:
  30. return a % b
  31. cdef sparse_t __floordiv__(sparse_t a, sparse_t b):
  32. if b == 0:
  33. if sparse_t is float64_t:
  34. # Match non-sparse Series behavior implemented in mask_zero_div_zero
  35. if a > 0:
  36. return INF
  37. elif a < 0:
  38. return -INF
  39. return NaN
  40. else:
  41. return 0
  42. else:
  43. return a // b
  44. # ----------------------------------------------------------------------
  45. # sparse array op
  46. # ----------------------------------------------------------------------
  47. {{py:
  48. # dtype, arith_comp_group, logical_group
  49. dtypes = [('float64', True, False),
  50. ('int64', True, True),
  51. ('uint8', False, True)]
  52. # do not generate arithmetic / comparison template for uint8,
  53. # it should be done in fused types
  54. def get_op(tup):
  55. assert isinstance(tup, tuple)
  56. assert len(tup) == 4
  57. opname, lval, rval, dtype = tup
  58. ops_dict = {'add': '{0} + {1}',
  59. 'sub': '{0} - {1}',
  60. 'mul': '{0} * {1}',
  61. 'div': '__div__({0}, {1})',
  62. 'mod': '__mod__({0}, {1})',
  63. 'truediv': '__truediv__({0}, {1})',
  64. 'floordiv': '__floordiv__({0}, {1})',
  65. 'pow': '{0} ** {1}',
  66. 'eq': '{0} == {1}',
  67. 'ne': '{0} != {1}',
  68. 'lt': '{0} < {1}',
  69. 'gt': '{0} > {1}',
  70. 'le': '{0} <= {1}',
  71. 'ge': '{0} >= {1}',
  72. 'and': '{0} & {1}', # logical op
  73. 'or': '{0} | {1}',
  74. 'xor': '{0} ^ {1}'}
  75. return ops_dict[opname].format(lval, rval)
  76. def get_dispatch(dtypes):
  77. ops_list = ['add', 'sub', 'mul', 'div', 'mod', 'truediv',
  78. 'floordiv', 'pow',
  79. 'eq', 'ne', 'lt', 'gt', 'le', 'ge',
  80. 'and', 'or', 'xor']
  81. for opname in ops_list:
  82. for dtype, arith_comp_group, logical_group in dtypes:
  83. if opname in ('div', 'truediv'):
  84. rdtype = 'float64'
  85. elif opname in ('eq', 'ne', 'lt', 'gt', 'le', 'ge'):
  86. # comparison op
  87. rdtype = 'uint8'
  88. elif opname in ('and', 'or', 'xor'):
  89. # logical op
  90. rdtype = 'uint8'
  91. else:
  92. rdtype = dtype
  93. if opname in ('and', 'or', 'xor'):
  94. if logical_group:
  95. yield opname, dtype, rdtype
  96. else:
  97. if arith_comp_group:
  98. yield opname, dtype, rdtype
  99. }}
  100. {{for opname, dtype, rdtype in get_dispatch(dtypes)}}
  101. {{if opname == "pow"}}
  102. @cython.cpow(True) # Cython 3 matches Python pow, which isn't what we want here
  103. {{endif}}
  104. @cython.wraparound(False)
  105. @cython.boundscheck(False)
  106. cdef tuple block_op_{{opname}}_{{dtype}}({{dtype}}_t[:] x_,
  107. BlockIndex xindex,
  108. {{dtype}}_t xfill,
  109. {{dtype}}_t[:] y_,
  110. BlockIndex yindex,
  111. {{dtype}}_t yfill):
  112. """
  113. Binary operator on BlockIndex objects with fill values
  114. """
  115. cdef:
  116. BlockIndex out_index
  117. Py_ssize_t xi = 0, yi = 0, out_i = 0 # fp buf indices
  118. int32_t xbp = 0, ybp = 0 # block positions
  119. int32_t xloc, yloc
  120. Py_ssize_t xblock = 0, yblock = 0 # block numbers
  121. {{dtype}}_t[:] x, y
  122. ndarray[{{rdtype}}_t, ndim=1] out
  123. # to suppress Cython warning
  124. x = x_
  125. y = y_
  126. out_index = xindex.make_union(yindex)
  127. out = np.empty(out_index.npoints, dtype=np.{{rdtype}})
  128. # Wow, what a hack job. Need to do something about this
  129. # walk the two SparseVectors, adding matched locations...
  130. for out_i in range(out_index.npoints):
  131. if yblock == yindex.nblocks:
  132. # use y fill value
  133. out[out_i] = {{(opname, 'x[xi]', 'yfill', dtype) | get_op}}
  134. xi += 1
  135. # advance x location
  136. xbp += 1
  137. if xbp == xindex.lenbuf[xblock]:
  138. xblock += 1
  139. xbp = 0
  140. continue
  141. if xblock == xindex.nblocks:
  142. # use x fill value
  143. out[out_i] = {{(opname, 'xfill', 'y[yi]', dtype) | get_op}}
  144. yi += 1
  145. # advance y location
  146. ybp += 1
  147. if ybp == yindex.lenbuf[yblock]:
  148. yblock += 1
  149. ybp = 0
  150. continue
  151. yloc = yindex.locbuf[yblock] + ybp
  152. xloc = xindex.locbuf[xblock] + xbp
  153. # each index in the out_index had to come from either x, y, or both
  154. if xloc == yloc:
  155. out[out_i] = {{(opname, 'x[xi]', 'y[yi]', dtype) | get_op}}
  156. xi += 1
  157. yi += 1
  158. # advance both locations
  159. xbp += 1
  160. if xbp == xindex.lenbuf[xblock]:
  161. xblock += 1
  162. xbp = 0
  163. ybp += 1
  164. if ybp == yindex.lenbuf[yblock]:
  165. yblock += 1
  166. ybp = 0
  167. elif xloc < yloc:
  168. # use y fill value
  169. out[out_i] = {{(opname, 'x[xi]', 'yfill', dtype) | get_op}}
  170. xi += 1
  171. # advance x location
  172. xbp += 1
  173. if xbp == xindex.lenbuf[xblock]:
  174. xblock += 1
  175. xbp = 0
  176. else:
  177. # use x fill value
  178. out[out_i] = {{(opname, 'xfill', 'y[yi]', dtype) | get_op}}
  179. yi += 1
  180. # advance y location
  181. ybp += 1
  182. if ybp == yindex.lenbuf[yblock]:
  183. yblock += 1
  184. ybp = 0
  185. return out, out_index, {{(opname, 'xfill', 'yfill', dtype) | get_op}}
  186. {{if opname == "pow"}}
  187. @cython.cpow(True) # Cython 3 matches Python pow, which isn't what we want here
  188. {{endif}}
  189. @cython.wraparound(False)
  190. @cython.boundscheck(False)
  191. cdef tuple int_op_{{opname}}_{{dtype}}({{dtype}}_t[:] x_,
  192. IntIndex xindex,
  193. {{dtype}}_t xfill,
  194. {{dtype}}_t[:] y_,
  195. IntIndex yindex,
  196. {{dtype}}_t yfill):
  197. cdef:
  198. IntIndex out_index
  199. Py_ssize_t xi = 0, yi = 0, out_i = 0 # fp buf indices
  200. int32_t xloc, yloc
  201. int32_t[:] xindices, yindices, out_indices
  202. {{dtype}}_t[:] x, y
  203. ndarray[{{rdtype}}_t, ndim=1] out
  204. # suppress Cython compiler warnings due to inlining
  205. x = x_
  206. y = y_
  207. # need to do this first to know size of result array
  208. out_index = xindex.make_union(yindex)
  209. out = np.empty(out_index.npoints, dtype=np.{{rdtype}})
  210. xindices = xindex.indices
  211. yindices = yindex.indices
  212. out_indices = out_index.indices
  213. # walk the two SparseVectors, adding matched locations...
  214. for out_i in range(out_index.npoints):
  215. if xi == xindex.npoints:
  216. # use x fill value
  217. out[out_i] = {{(opname, 'xfill', 'y[yi]', dtype) | get_op}}
  218. yi += 1
  219. continue
  220. if yi == yindex.npoints:
  221. # use y fill value
  222. out[out_i] = {{(opname, 'x[xi]', 'yfill', dtype) | get_op}}
  223. xi += 1
  224. continue
  225. xloc = xindices[xi]
  226. yloc = yindices[yi]
  227. # each index in the out_index had to come from either x, y, or both
  228. if xloc == yloc:
  229. out[out_i] = {{(opname, 'x[xi]', 'y[yi]', dtype) | get_op}}
  230. xi += 1
  231. yi += 1
  232. elif xloc < yloc:
  233. # use y fill value
  234. out[out_i] = {{(opname, 'x[xi]', 'yfill', dtype) | get_op}}
  235. xi += 1
  236. else:
  237. # use x fill value
  238. out[out_i] = {{(opname, 'xfill', 'y[yi]', dtype) | get_op}}
  239. yi += 1
  240. return out, out_index, {{(opname, 'xfill', 'yfill', dtype) | get_op}}
  241. cpdef sparse_{{opname}}_{{dtype}}({{dtype}}_t[:] x,
  242. SparseIndex xindex, {{dtype}}_t xfill,
  243. {{dtype}}_t[:] y,
  244. SparseIndex yindex, {{dtype}}_t yfill):
  245. if isinstance(xindex, BlockIndex):
  246. return block_op_{{opname}}_{{dtype}}(x, xindex.to_block_index(), xfill,
  247. y, yindex.to_block_index(), yfill)
  248. elif isinstance(xindex, IntIndex):
  249. return int_op_{{opname}}_{{dtype}}(x, xindex.to_int_index(), xfill,
  250. y, yindex.to_int_index(), yfill)
  251. else:
  252. raise NotImplementedError
  253. {{endfor}}