Skip to content

Commit 4c042ee

Browse files
authored
Backport fix from #1853 and support dace.map syntax for struct fields (#2186)
1 parent c2e952f commit 4c042ee

File tree

6 files changed

+159
-32
lines changed

6 files changed

+159
-32
lines changed

dace/data.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,9 @@ def __init__(self,
387387

388388
self.members = OrderedDict(members)
389389
for k, v in self.members.items():
390+
if isinstance(v, dtypes.typeclass):
391+
v = Scalar(v)
392+
self.members[k] = v
390393
v.transient = transient
391394

392395
self.name = name
@@ -402,6 +405,8 @@ def __init__(self,
402405
elif isinstance(v, Scalar):
403406
symbols |= v.free_symbols
404407
fields_and_types[k] = v.dtype
408+
elif isinstance(v, dtypes.typeclass):
409+
fields_and_types[k] = v
405410
elif isinstance(v, (sp.Basic, symbolic.SymExpr)):
406411
symbols |= v.free_symbols
407412
fields_and_types[k] = symbolic.symtype(v)

dace/frontend/python/newast.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,7 +1852,7 @@ def _parse_map_inputs(self, name: str, params: List[Tuple[str, str]],
18521852
if symbolic.issymbolic(atom, self.sdfg.constants):
18531853
# Check for undefined variables
18541854
atomstr = str(atom)
1855-
if atomstr not in self.defined:
1855+
if atomstr not in self.defined and atomstr not in self.sdfg.arrays:
18561856
raise DaceSyntaxError(self, node, 'Undefined variable "%s"' % atom)
18571857
# Add to global SDFG symbols
18581858

@@ -2350,7 +2350,7 @@ def visit_For(self, node: ast.For):
23502350
if symbolic.issymbolic(atom, self.sdfg.constants):
23512351
astr = str(atom)
23522352
# Check for undefined variables
2353-
if astr not in self.defined:
2353+
if astr not in self.defined and not ('.' in astr and astr in self.sdfg.arrays):
23542354
raise DaceSyntaxError(self, node, 'Undefined variable "%s"' % atom)
23552355
# Add to global SDFG symbols if not a scalar
23562356
if (astr not in self.sdfg.symbols and not (astr in self.variables or astr in self.sdfg.arrays)):
@@ -3079,8 +3079,14 @@ def _add_access(
30793079
else:
30803080
var_name = self.sdfg.temp_data_name()
30813081

3082-
parent_name = self.scope_vars[name]
3083-
parent_array = self.scope_arrays[parent_name]
3082+
parent_name = self.scope_vars[until(name, '.')]
3083+
if '.' in name:
3084+
struct_field = name[name.index('.'):]
3085+
parent_name += struct_field
3086+
scope_ndict = dace.sdfg.NestedDict(self.scope_arrays)
3087+
parent_array = scope_ndict[parent_name]
3088+
else:
3089+
parent_array = self.scope_arrays[parent_name]
30843090

30853091
has_indirection = (_subset_has_indirection(rng, self) or _subset_is_local_symbol_dependent(rng, self))
30863092
if has_indirection:
@@ -3244,7 +3250,7 @@ def _add_write_access(self,
32443250
return self.accesses[(name, rng, 'w')]
32453251
elif name in self.variables:
32463252
return (self.variables[name], rng)
3247-
elif (name, rng, 'r') in self.accesses or name in self.scope_vars:
3253+
elif (name, rng, 'r') in self.accesses or until(name, '.') in self.scope_vars:
32483254
return self._add_access(name, rng, 'w', target, new_name, arr_type)
32493255
else:
32503256
raise NotImplementedError
@@ -3498,7 +3504,7 @@ def _visit_assign(self, node, node_target, op, dtype=None, is_return=False):
34983504
raise IndexError('Boolean array indexing cannot be combined with indirect access')
34993505

35003506
if self.nested and not new_data:
3501-
new_name, new_rng = self._add_write_access(name, rng, target)
3507+
new_name, new_rng = self._add_write_access(true_name, rng, target)
35023508
# Local symbol or local data dependent
35033509
if _subset_is_local_symbol_dependent(rng, self):
35043510
new_rng = rng

dace/symbolic.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,8 @@ def symlist(values):
307307
except TypeError:
308308
values = [values]
309309

310+
skip = set()
311+
310312
for expr in values:
311313
if isinstance(expr, SymExpr):
312314
true_expr = expr.expr
@@ -315,6 +317,12 @@ def symlist(values):
315317
else:
316318
continue
317319
for atom in sympy.preorder_traversal(true_expr):
320+
if atom in skip:
321+
continue
322+
if isinstance(atom, Attr):
323+
# Skip attributes
324+
skip.add(atom.args[1])
325+
continue
318326
if isinstance(atom, symbol):
319327
result[atom.name] = atom
320328
return result

tests/codegen/allocation_lifetime_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ def persistentmem(output: dace.int32[1]):
206206
del csdfg
207207

208208

209+
@pytest.mark.skip(reason="In v1, produces two tasklets side-by-side, leading to nondeterministic code order")
209210
def test_alloc_persistent_threadlocal():
210211

211212
@dace.program
@@ -599,7 +600,7 @@ def test_multisize():
599600
test_persistent_gpu_transpose_regression()
600601
test_alloc_persistent_register()
601602
test_alloc_persistent()
602-
test_alloc_persistent_threadlocal()
603+
# test_alloc_persistent_threadlocal()
603604
test_alloc_persistent_threadlocal_naming()
604605
test_alloc_multistate()
605606
test_nested_view_samename()

tests/python_frontend/loops_test.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from dace.frontend.python.common import DaceSyntaxError
77

8+
89
@dace.program
910
def for_loop():
1011
A = dace.ndarray([10], dtype=dace.int32)
@@ -499,6 +500,22 @@ def test_branch_in_while():
499500
assert len(sdfg.source_nodes()) == 1
500501

501502

503+
def test_for_with_field():
504+
struct = dace.data.Structure({'data': dace.float64[20], 'length': dace.int32}, name='MyStruct')
505+
506+
@dace.program
507+
def for_with_field(S: struct):
508+
for i in range(S.length):
509+
S.data[i] = S.data[i] + 1.0
510+
511+
A = np.random.rand(20)
512+
inp_struct = struct.dtype.base_type.as_ctypes()(data=A.__array_interface__['data'][0], length=10)
513+
expected = np.copy(A)
514+
expected[:10] += 1.0
515+
for_with_field.compile()(S=inp_struct)
516+
assert np.allclose(A, expected)
517+
518+
502519
if __name__ == "__main__":
503520
test_for_loop()
504521
test_for_loop_with_break_continue()
@@ -522,3 +539,4 @@ def test_branch_in_while():
522539
test_while_else()
523540
test_branch_in_for()
524541
test_branch_in_while()
542+
test_for_with_field()

tests/python_frontend/structures/structure_python_test.py

Lines changed: 114 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright 2019-2023 ETH Zurich and the DaCe authors. All rights reserved.
2+
import ctypes
23
import dace
34
import numpy as np
45
import pytest
@@ -18,7 +19,7 @@ def csr_to_dense_python(A: CSR, B: dace.float32[M, N]):
1819
for i in dace.map[0:M]:
1920
for idx in dace.map[A.indptr[i]:A.indptr[i + 1]]:
2021
B[i, A.indices[idx]] = A.data[idx]
21-
22+
2223
rng = np.random.default_rng(42)
2324
A = sparse.random(20, 20, density=0.1, format='csr', dtype=np.float32, random_state=rng)
2425
B = np.zeros((20, 20), dtype=np.float32)
@@ -41,7 +42,7 @@ def test_write_structure():
4142
M, N, nnz = (dace.symbol(s) for s in ('M', 'N', 'nnz'))
4243
CSR = dace.data.Structure(dict(indptr=dace.int32[M + 1], indices=dace.int32[nnz], data=dace.float32[nnz]),
4344
name='CSRMatrix')
44-
45+
4546
@dace.program
4647
def dense_to_csr_python(A: dace.float32[M, N], B: CSR):
4748
idx = 0
@@ -53,7 +54,7 @@ def dense_to_csr_python(A: dace.float32[M, N], B: CSR):
5354
B.indices[idx] = j
5455
idx += 1
5556
B.indptr[M] = idx
56-
57+
5758
rng = np.random.default_rng(42)
5859
tmp = sparse.random(20, 20, density=0.1, format='csr', dtype=np.float32, random_state=rng)
5960
A = tmp.toarray()
@@ -75,7 +76,7 @@ def test_local_structure():
7576
M, N, nnz = (dace.symbol(s) for s in ('M', 'N', 'nnz'))
7677
CSR = dace.data.Structure(dict(indptr=dace.int32[M + 1], indices=dace.int32[nnz], data=dace.float32[nnz]),
7778
name='CSRMatrix')
78-
79+
7980
@dace.program
8081
def dense_to_csr_local_python(A: dace.float32[M, N], B: CSR):
8182
tmp = dace.define_local_structure(CSR)
@@ -91,7 +92,7 @@ def dense_to_csr_local_python(A: dace.float32[M, N], B: CSR):
9192
B.indptr[:] = tmp.indptr[:]
9293
B.indices[:] = tmp.indices[:]
9394
B.data[:] = tmp.data[:]
94-
95+
9596
rng = np.random.default_rng(42)
9697
tmp = sparse.random(20, 20, density=0.1, format='csr', dtype=np.float32, random_state=rng)
9798
A = tmp.toarray()
@@ -118,12 +119,11 @@ def __init__(self, diag, upper, lower):
118119
self.lower = lower
119120

120121
n, nblocks = dace.symbol('n'), dace.symbol('nblocks')
121-
BlockTriDiagonal = dace.data.Structure(
122-
dict(diagonal=dace.complex128[nblocks, n, n],
123-
upper=dace.complex128[nblocks, n, n],
124-
lower=dace.complex128[nblocks, n, n]),
125-
name='BlockTriDiagonalMatrix')
126-
122+
BlockTriDiagonal = dace.data.Structure(dict(diagonal=dace.complex128[nblocks, n, n],
123+
upper=dace.complex128[nblocks, n, n],
124+
lower=dace.complex128[nblocks, n, n]),
125+
name='BlockTriDiagonalMatrix')
126+
127127
@dace.program
128128
def rgf_leftToRight(A: BlockTriDiagonal, B: BlockTriDiagonal, n_: dace.int32, nblocks_: dace.int32):
129129

@@ -139,42 +139,41 @@ def rgf_leftToRight(A: BlockTriDiagonal, B: BlockTriDiagonal, n_: dace.int32, nb
139139
# 2. Forward substitution
140140
# From left to right
141141
for i in range(1, nblocks_):
142-
tmp[i] = np.linalg.inv(A.diagonal[i] - A.lower[i-1] @ tmp[i-1] @ A.upper[i-1])
142+
tmp[i] = np.linalg.inv(A.diagonal[i] - A.lower[i - 1] @ tmp[i - 1] @ A.upper[i - 1])
143143
# 3. Initialisation of last element of B
144144
B.diagonal[-1] = tmp[-1]
145145

146146
# 4. Backward substitution
147147
# From right to left
148148

149-
for i in range(nblocks_-2, -1, -1):
150-
B.diagonal[i] = tmp[i] @ (identity + A.upper[i] @ B.diagonal[i+1] @ A.lower[i] @ tmp[i])
151-
B.upper[i] = -tmp[i] @ A.upper[i] @ B.diagonal[i+1]
152-
B.lower[i] = np.transpose(B.upper[i])
153-
149+
for i in range(nblocks_ - 2, -1, -1):
150+
B.diagonal[i] = tmp[i] @ (identity + A.upper[i] @ B.diagonal[i + 1] @ A.lower[i] @ tmp[i])
151+
B.upper[i] = -tmp[i] @ A.upper[i] @ B.diagonal[i + 1]
152+
B.lower[i] = np.transpose(B.upper[i])
153+
154154
rng = np.random.default_rng(42)
155155

156156
A_diag = rng.random((10, 20, 20)) + 1j * rng.random((10, 20, 20))
157157
A_upper = rng.random((10, 20, 20)) + 1j * rng.random((10, 20, 20))
158-
A_lower = rng.random((10, 20, 20)) + 1j * rng.random((10, 20, 20))
158+
A_lower = rng.random((10, 20, 20)) + 1j * rng.random((10, 20, 20))
159159
inpBTD = BlockTriDiagonal.dtype._typeclass.as_ctypes()(diagonal=A_diag.__array_interface__['data'][0],
160160
upper=A_upper.__array_interface__['data'][0],
161161
lower=A_lower.__array_interface__['data'][0])
162-
162+
163163
B_diag = np.zeros((10, 20, 20), dtype=np.complex128)
164164
B_upper = np.zeros((10, 20, 20), dtype=np.complex128)
165165
B_lower = np.zeros((10, 20, 20), dtype=np.complex128)
166166
outBTD = BlockTriDiagonal.dtype._typeclass.as_ctypes()(diagonal=B_diag.__array_interface__['data'][0],
167167
upper=B_upper.__array_interface__['data'][0],
168168
lower=B_lower.__array_interface__['data'][0])
169-
169+
170170
func = rgf_leftToRight.compile()
171171
func(A=inpBTD, B=outBTD, n_=A_diag.shape[1], nblocks_=A_diag.shape[0], n=A_diag.shape[1], nblocks=A_diag.shape[0])
172172

173173
A = BTD(A_diag, A_upper, A_lower)
174-
B = BTD(np.zeros((10, 20, 20), dtype=np.complex128),
175-
np.zeros((10, 20, 20), dtype=np.complex128),
174+
B = BTD(np.zeros((10, 20, 20), dtype=np.complex128), np.zeros((10, 20, 20), dtype=np.complex128),
176175
np.zeros((10, 20, 20), dtype=np.complex128))
177-
176+
178177
rgf_leftToRight.f(A, B, A_diag.shape[1], A_diag.shape[0])
179178

180179
assert np.allclose(B.diagonal, B_diag)
@@ -195,15 +194,15 @@ def csr_to_dense_python(A: CSR, B: dace.float32[M, N]):
195194
for i in dace.map[0:M]:
196195
for idx in dace.map[A.indptr[i]:A.indptr[i + 1]]:
197196
B[i, A.indices[idx]] = A.data[idx]
198-
197+
199198
rng = np.random.default_rng(42)
200199
A = sparse.random(20, 20, density=0.1, format='csr', dtype=np.float32, random_state=rng)
201200
ref = A.toarray()
202201

203202
inpA = CSR.dtype._typeclass.as_ctypes()(indptr=A.indptr.__array_interface__['data'][0],
204203
indices=A.indices.__array_interface__['data'][0],
205204
data=A.data.__array_interface__['data'][0])
206-
205+
207206
# TODO: The following doesn't work because we need to create a Structure data descriptor from the ctypes class.
208207
# csr_to_dense_python(inpA, B)
209208
naive = csr_to_dense_python.to_sdfg(simplify=False)
@@ -224,9 +223,99 @@ def csr_to_dense_python(A: CSR, B: dace.float32[M, N]):
224223
assert np.allclose(B, ref)
225224

226225

226+
def test_write_structure_in_map():
227+
M = dace.symbol('M')
228+
N = dace.symbol('N')
229+
Bundle = dace.data.Structure(members={
230+
"data": dace.data.Array(dace.float32, (M, N)),
231+
"size": dace.data.Scalar(dace.int64)
232+
},
233+
name="BundleType")
234+
235+
@dace.program
236+
def init_prog(bundle: Bundle, fill_value: int) -> None:
237+
for index in dace.map[0:bundle.size]:
238+
bundle.data[index, :] = fill_value
239+
240+
data = np.zeros((10, 5), dtype=np.float32)
241+
fill_value = 42
242+
inp_struct = Bundle.dtype.base_type.as_ctypes()(
243+
data=data.__array_interface__['data'][0],
244+
size=9,
245+
)
246+
ref = np.zeros((10, 5), dtype=np.float32)
247+
ref[:9, :] = fill_value
248+
249+
init_prog.compile()(inp_struct, fill_value, M=10, N=5)
250+
251+
assert np.allclose(data, ref)
252+
253+
254+
def test_readwrite_structure_in_map():
255+
M = dace.symbol('M')
256+
N = dace.symbol('N')
257+
Bundle = dace.data.Structure(members={
258+
"data": dace.data.Array(dace.float32, (M, N)),
259+
"data2": dace.data.Array(dace.float32, (M, N)),
260+
"size": dace.data.Scalar(dace.int64)
261+
},
262+
name="BundleTypeTwoArrays")
263+
264+
@dace.program
265+
def copy_prog(bundle: Bundle) -> None:
266+
for index in dace.map[0:bundle.size]:
267+
bundle.data[index, :] = bundle.data2[index, :] + 5
268+
269+
data = np.zeros((10, 5), dtype=np.float32)
270+
data2 = np.ones((10, 5), dtype=np.float32)
271+
inp_struct = Bundle.dtype.base_type.as_ctypes()(
272+
data=data.__array_interface__['data'][0],
273+
data2=data2.__array_interface__['data'][0],
274+
size=ctypes.c_int64(6),
275+
)
276+
ref = np.zeros((10, 5), dtype=np.float32)
277+
ref[:6, :] = 6.0
278+
279+
csdfg = copy_prog.compile()
280+
csdfg.fast_call((ctypes.byref(inp_struct), ctypes.c_int(5)), (ctypes.c_int(5),))
281+
282+
assert np.allclose(data, ref)
283+
284+
285+
def test_write_structure_in_loop():
286+
M = dace.symbol('M')
287+
N = dace.symbol('N')
288+
Bundle = dace.data.Structure(members={
289+
"data": dace.data.Array(dace.float32, (M, N)),
290+
"size": dace.data.Scalar(dace.int64)
291+
},
292+
name="BundleType")
293+
294+
@dace.program
295+
def init_prog(bundle: Bundle, fill_value: int) -> None:
296+
for index in range(bundle.size):
297+
bundle.data[index, :] = fill_value
298+
299+
data = np.zeros((10, 5), dtype=np.float32)
300+
fill_value = 42
301+
inp_struct = Bundle.dtype.base_type.as_ctypes()(
302+
data=data.__array_interface__['data'][0],
303+
size=6,
304+
)
305+
ref = np.zeros((10, 5), dtype=np.float32)
306+
ref[:6, :] = fill_value
307+
308+
init_prog.compile()(inp_struct, fill_value, M=10, N=5)
309+
310+
assert np.allclose(data, ref)
311+
312+
227313
if __name__ == '__main__':
228314
test_read_structure()
229315
test_write_structure()
230316
test_local_structure()
231317
test_rgf()
232318
# test_read_structure_gpu()
319+
test_write_structure_in_map()
320+
test_readwrite_structure_in_map()
321+
test_write_structure_in_loop()

0 commit comments

Comments
 (0)