Skip to content

Commit 06407d5

Browse files
committed
Support numba expr with padding due to chunk and shape
1 parent e2f76e5 commit 06407d5

File tree

3 files changed

+111
-23
lines changed

3 files changed

+111
-23
lines changed

blosc2/blosc2_ext.pyx

+46-17
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,12 @@ cdef extern from "b2nd.h":
485485
int b2nd_from_schunk(blosc2_schunk *schunk, b2nd_array_t **array)
486486

487487
void blosc2_unidim_to_multidim(uint8_t ndim, int64_t *shape, int64_t i, int64_t *index)
488+
int b2nd_copy_buffer(int8_t ndim,
489+
uint8_t itemsize,
490+
const void *src, const int64_t *src_pad_shape,
491+
const int64_t *src_start, const int64_t *src_stop,
492+
void *dst, const int64_t *dst_pad_shape,
493+
const int64_t *dst_start);
488494

489495

490496
ctypedef struct user_filters_udata:
@@ -508,6 +514,8 @@ ctypedef struct numba_udata:
508514
int32_t *blockshape
509515
int32_t *chunkshape_ndim
510516
int64_t *shape
517+
int64_t *ext_shape
518+
b2nd_array_t *array
511519

512520
MAX_TYPESIZE = BLOSC_MAX_TYPESIZE
513521
MAX_BUFFERSIZE = BLOSC2_MAX_BUFFERSIZE
@@ -1566,31 +1574,40 @@ cdef int general_numba(blosc2_prefilter_params *params):
15661574
cdef int64_t chunks_in_array[B2ND_MAX_DIM]
15671575
for i in range(nd):
15681576
# Canviar-ho a extshape i chunkshape normal
1569-
chunks_in_array[i] = udata.shape[i] // udata.chunkshape_ndim[i]
1577+
chunks_in_array[i] = udata.ext_shape[i] // udata.chunkshape_ndim[i]
15701578
blosc2_unidim_to_multidim(nd, chunks_in_array, params.nchunk, chunk_ndim)
15711579
# print("nchunk ", params.nchunk)
15721580

15731581
cdef int64_t block_ndim[B2ND_MAX_DIM]
15741582
cdef int64_t blocks_in_chunk[B2ND_MAX_DIM]
15751583
for i in range(nd):
1576-
# Canviar-ho a extshape i chunkshape normal
15771584
blocks_in_chunk[i] = udata.chunkshape_ndim[i] // udata.blockshape[i]
15781585
blosc2_unidim_to_multidim(nd, chunks_in_array, params.nblock, block_ndim)
15791586

1580-
cdef int64_t offset_ndim[B2ND_MAX_DIM]
1587+
cdef int64_t start_ndim[B2ND_MAX_DIM]
15811588
for i in range(nd):
1582-
offset_ndim[i] = chunk_ndim[i] * udata.chunkshape_ndim[i] + block_ndim[i] * udata.blockshape[i]
1583-
# print("offset_ndim[", i, "] = ", offset_ndim[i])
1589+
start_ndim[i] = chunk_ndim[i] * udata.chunkshape_ndim[i] + block_ndim[i] * udata.blockshape[i]
1590+
# print("start_ndim[", i, "] = ", start_ndim[i])
15841591

1592+
padding = False
1593+
blockshape = []
1594+
for i in range(nd):
1595+
if start_ndim[i] + udata.blockshape[i] > udata.shape[i]:
1596+
padding = True
1597+
blockshape.append(udata.shape[i] - start_ndim[i])
1598+
else:
1599+
blockshape.append(udata.blockshape[i])
15851600
cdef np.npy_intp dims[B2ND_MAX_DIM]
1586-
# params.output_size // params.output_typesize
15871601
for i in range(nd):
1588-
dims[i] = udata.blockshape[i] # Açò canviarà quan hi haja padding
1589-
output = np.PyArray_SimpleNewFromData(nd, dims, udata.output_cdtype, <void*>params.output)
1602+
dims[i] = blockshape[i]
15901603

1604+
if padding:
1605+
output = np.empty(blockshape, udata.array.dtype) # igual toca crear un py dtype
1606+
print(output.dtype)
1607+
else:
1608+
output = np.PyArray_SimpleNewFromData(nd, dims, udata.output_cdtype, <void*>params.output)
15911609
inputs_tuple = _ctypes.PyObj_FromPtr(udata.inputs_id)
15921610
inputs = []
1593-
blockshape = [udata.blockshape[i] for i in range(nd)]
15941611
if nd == 1:
15951612
# Enviar-ho a fer la mà quan nd = 1 ? o ho puc suportar sense problemes??
15961613
for obj, dtype in inputs_tuple:
@@ -1608,8 +1625,8 @@ cdef int general_numba(blosc2_prefilter_params *params):
16081625
# Get tuple of slices
16091626
l = []
16101627
for i in range(nd):
1611-
# print("slice dim ", i, " = inici = ", offset_ndim[i], " final = ", offset_ndim[i] + udata.blockshape[i])
1612-
l.append(slice(offset_ndim[i], offset_ndim[i] + udata.blockshape[i]))
1628+
l.append(slice(start_ndim[i], start_ndim[i] + blockshape[i]))
1629+
# print("slice dim ", i, " = inici = ", start_ndim[i], " final = ", start_ndim[i] + blockshape[i])
16131630
slices = tuple(l)
16141631
for obj, dtype in inputs_tuple:
16151632
if isinstance(obj, blosc2.SChunk):
@@ -1624,14 +1641,24 @@ cdef int general_numba(blosc2_prefilter_params *params):
16241641
raise ValueError("Unsupported operand")
16251642

16261643
func_id = udata.py_func.decode("utf-8")
1627-
# out = np.empty(blockshape, dtype)
16281644
blosc2.prefilter_funcs[func_id](tuple(inputs), output, offset)
16291645

1630-
# Tornarem a intentar-ho amb el capi ndarray sembla que les posicions no es formategen bé
1631-
# cdef Py_buffer *buf = <Py_buffer *> malloc(sizeof(Py_buffer))
1632-
# PyObject_GetBuffer(out, buf, PyBUF_SIMPLE)
1633-
# memcpy(params.output, buf.buf, buf.len)
1634-
# PyBuffer_Release(buf)
1646+
cdef int64_t start[B2ND_MAX_DIM]
1647+
cdef int64_t slice_shape[B2ND_MAX_DIM]
1648+
cdef int64_t blockshape_int64[B2ND_MAX_DIM]
1649+
1650+
cdef Py_buffer *buf
1651+
if padding:
1652+
for i in range(nd):
1653+
start[i] = 0
1654+
slice_shape[i] = blockshape[i]
1655+
blockshape_int64[i] = udata.blockshape[i]
1656+
buf = <Py_buffer *> malloc(sizeof(Py_buffer))
1657+
PyObject_GetBuffer(output, buf, PyBUF_SIMPLE)
1658+
b2nd_copy_buffer(udata.ndim, params.output_typesize,
1659+
buf.buf, slice_shape, start, slice_shape,
1660+
params.output, blockshape_int64, start)
1661+
PyBuffer_Release(buf)
16351662

16361663
return 0
16371664

@@ -2282,6 +2309,8 @@ cdef class NDArray:
22822309
pref_udata.blockshape = self.array.blockshape
22832310
pref_udata.shape = self.array.shape
22842311
pref_udata.chunkshape_ndim = self.array.chunkshape
2312+
pref_udata.ext_shape = self.array.extshape
2313+
pref_udata.array = self.array
22852314

22862315
preparams.user_data = pref_udata
22872316
cparams.preparams = preparams

blosc2/lazyexpr.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ def do_slices_intersect(slice1, slice2):
510510

511511

512512
class NumbaExpr:
513-
def __init__(self, func, inputs_tuple, dtype, shape):
513+
def __init__(self, func, inputs_tuple, dtype, shape, **kwargs):
514514
# Suposem que tots els operands tenen els mateix shape (ara per ara)
515515
self.inputs_tuple = inputs_tuple # Keep reference to evict lost reference
516516
if shape is None:
@@ -521,9 +521,7 @@ def __init__(self, func, inputs_tuple, dtype, shape):
521521
break
522522
cparams = {'nthreads': 1}
523523
# canviar això de nthreads
524-
chunks = [i // 2 for i in self.shape]
525-
blocks = [i // 2 for i in chunks]
526-
self.res = blosc2.empty(self.shape, dtype, cparams=cparams, chunks=chunks, blocks=blocks)
524+
self.res = blosc2.empty(self.shape, dtype, cparams=cparams, **kwargs)
527525
self.res._set_aux_numba(func, id(inputs_tuple))
528526
self.func = func
529527

@@ -536,5 +534,5 @@ def eval(self):
536534

537535

538536
# inputs_tuple = ( (operand, dtype), (operand2, dtype2), ... )
539-
def expr_from_udf(func, inputs_tuple, dtype, shape=None):
540-
return NumbaExpr(func, inputs_tuple, dtype, shape)
537+
def expr_from_udf(func, inputs_tuple, dtype, shape=None, **kwargs):
538+
return NumbaExpr(func, inputs_tuple, dtype, shape, **kwargs)

examples/ndarray/eval_expr_numba.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#######################################################################
2+
# Copyright (c) 2019-present, Blosc Development Team <[email protected]>
3+
# All rights reserved.
4+
#
5+
# This source code is licensed under a BSD-style license (found in the
6+
# LICENSE file in the root directory of this source tree)
7+
#######################################################################
8+
9+
# This shows how to evaluate expressions with NDArray instances as operands.
10+
11+
import numpy as np
12+
import blosc2
13+
import numba as nb
14+
15+
16+
shape = (13, 13)
17+
dtype = np.float64
18+
19+
# Create a NDArray from a NumPy array
20+
npa = np.linspace(0, 1, np.prod(shape)).reshape(shape)
21+
npc = npa + 1
22+
23+
#a = blosc2.SChunk()
24+
#a.append_data(npa)
25+
26+
a = blosc2.asarray(npa)
27+
28+
# Get a LazyExpr instance
29+
#c = a + 1
30+
# Evaluate! Output is a NDArray
31+
#d = c.evaluate()
32+
# Check
33+
#assert np.allclose(d[:], npc)
34+
35+
36+
@nb.jit(nopython=True, parallel=True)
37+
def func_numba(inputs_tuple, output, offset):
38+
x = inputs_tuple[0]
39+
output[:] = x + 1
40+
# out = np.empty(x.shape, x.dtype)
41+
# for i in nb.prange(x.shape[0]):
42+
# for j in nb.prange(x.shape[1]):
43+
# out[i, j] = x[i, j] + 1
44+
# return out
45+
46+
# nb_res = func_numba(npa)
47+
48+
chunks = [10, 10]
49+
expr = blosc2.expr_from_udf(func_numba, ((npa, npa.dtype), ), npa.dtype, chunks=chunks, blocks=chunks)
50+
res = expr.eval()
51+
print(res.info)
52+
53+
54+
# print(npa)
55+
# print(res[:])
56+
# print(npc)
57+
58+
#assert np.allclose(res[...], npc)
59+
tol = 1e-5 if dtype is np.float32 else 1e-14
60+
if dtype in (np.float32, np.float64):
61+
np.testing.assert_allclose(res[...], npc, rtol=tol, atol=tol)

0 commit comments

Comments
 (0)