Deterministic dace toolchain draft#17
Conversation
spcl#2298) Get the number of warnings in tests down by avoiding usage of `state.add_*` functions like `state.add_array(...)`. --------- Co-authored-by: Roman Cattaneo <>
The `ControlFlowReachability` pass gets prohibitively expensive in particular graphs. Updating from `v1/maintenance` to current `main`, we have seen `simplify()` runtimes of 10-15 minutes where previous runtime was in the order of magnitude of tens of seconds. The slowdown turned out to be caused by not caching closures per region. Some of our graphs generate large, nested control flow regions (if statements) from iterative solvers with conditional returns that we map to if/else blocks with a boolean mask. In such a scenario, having four layers of nestedness is easily achieved and then `_region_closure()` gets called again and again for previously calculated closures of regions. Because of the transitive requirement of theses closures, control flow regions nested deep will have to "go up" an re-evaluate the same closures for "upper" regions again and again. This PR suggest a simple cache of closures per region to avoid this duplicate evaluation. Co-authored-by: Roman Cattaneo <>
Reduces SDFG size when serialized, using the following methods: * Non-human-readable JSON dumping by default * Consolidating file names in DebugInfo to be per-SDFG * Reducing the size of the DebugInfo JSON object based on fields * Saving transformation history set to off by default
This PR suggest to write a `CACHEDIR.TAG` file into the program folder. The tag is an attempt to signal (e.g. to backup software) that the containing folder contains no archival value, see http://www.brynosaurus.com/cachedir/. While the convention started with cache directories for things like thumbnails of a webbrowser, I'd argue the same argumentation (no archival value, frequent changes, un-suiteable to be located in `/var/cache` or `/tmp`) apply for build folders. Instead of writing the file by hand, we could also a library like https://pypi.org/project/cachedir-tag/. --------- Co-authored-by: Roman Cattaneo <>
Set is not hashable doesn't work with lru cache decorator, I propose using FrozenSet here.
Some parts of DaCe are currently relying on `six`, a python2 / python3 compatibility library. Given that DaCe is only supporting python 3.10 - 3.14 now, I think we don't need the `six` dependency anymore.
Following up on PR spcl#2312, this PR proposes to save compressed SDFGs in the program folder. As discussed in the last meeting: - no change to the API, i.e. we keep keyword arguments of `sdfg.save()` as they are. - save `program.sdfg` as compressed `program.sdfgz` inside the program folder - make sure that changes are backwards compatible and that the program folder is still found regardless of `program.sdfg` or `program.sdfgz` --------- Co-authored-by: Roman Cattaneo <>
If we unroll a top-level for CFG, then the connectivity might be broken, I added a unit test and the fix to it. Replace dict on loop does not properly update the init statement, which can be exposed by loop unrolling when parent loop parameter is used inside in an inner loop. I fixed it and added a unit test in loop unroll that would expose it.
`Range` subsets have a `reorder()` function that re-orders the dimensions in-place. So far, it only re-ordered the ranges, but not the tile sizes (which are stored in a separate list). This PR makes sure both, ranges and tile sizes, are re-ordered according to the given permutation. The PR adds a simple test case.
philip-paul-mueller
left a comment
There was a problem hiding this comment.
This is a first high level review of the changes.
| self._dst = dst | ||
| self._data: T = data | ||
|
|
||
| def id(self): |
There was a problem hiding this comment.
There are several things here.
First of all this type of edge is only for edges between states or to be precise control flow regions.
Inside a state, there is a second kind of graph, the dataflow graph.
down in the file you will find the MultiEdge (which is probably irrelevant) and the MultiConnectorEdge (which is the relevant), which are used for the dataflow graph.
In addition these other kind of edges are actually missing the id() function.
Then, you have added the id property to the Node, however, this is only the base class for the the nodes of the dataflow graph.
Thus, calling this function will fail, because the nodes, which are control flow regions do not have that attribute.
You must add them either to AbstractControlFlowRegion or what is probably better to ControlFlowBlock, but in that regard I am not fully sure.
Furthermore, two nodes can have multiple connections between them.
This is more relevant for the dataflow graph, however, it is technically still allowed for the state graph, although less relevant.
Thus, edge.id() is not an unique key, as there could be several edges between any two nodes.
To make them unique, you need to include the .data property, which is either a Memlet or an InterstateEdge.
| @@ -215,7 +218,7 @@ def __getitem__(self, node: NodeT) -> Iterable[NodeT]: | |||
|
|
|||
| def all_edges(self, *nodes: NodeT) -> Iterable[Edge[EdgeT]]: | |||
There was a problem hiding this comment.
| def all_edges(self, *nodes: NodeT) -> Iterable[Edge[EdgeT]]: | |
| def all_edges(self, *nodes: NodeT) -> List[Edge[EdgeT]]: |
| return current | ||
|
|
||
|
|
||
| # TODO: check if the edges need an id. If source and dest, are equal they should be interchangable right? |
There was a problem hiding this comment.
No, see my comment above.
Essentially the code:
a[3] = b[4]
a[6] = b[5]Will lead to an SDFG with two nodes one for a and one for b and two edges between them, each does an assignment.
There are similar situations with Maps.
| "type": typestr, | ||
| "label": labelstr, | ||
| "attributes": dace.serialize.all_properties_to_json(self), | ||
| # TODO(tehrengruber): This id looks very similar to the ID I introduced. |
There was a problem hiding this comment.
This is not stable it is essentially the position in the sorted node array, see here.
This design has some very nasty consequences in the way how transformations have to be implemented.
This is the transformation why your transformations look like:
def apply():
matched_node = self.pattern_node
# Never use `self.pattern_node` from here always use `matched_node` and pass it to all functions!
| ############################## | ||
|
|
||
| # TODO(tehrengruber): This could also be done using the counter. | ||
| def _find_new_name(self, name: str): |
There was a problem hiding this comment.
Yes it would probably better, because not all symbol (names) are stored in self.symbols, for example the symbols used as Map parameters are not inside it.
| """ | ||
| return set() | ||
|
|
||
| # TODO(tehrengruber): should we make this an ordered set? |
There was a problem hiding this comment.
I am actually in favour of that, however, there are some issues.
This function exists because SymPy had it and a lot of other classes have such a method (although it is most likely implemented by used_symbols()).
Thus you will have to change them all.
Furthermore, while here the return type is set[str] this is actually wrong.
Some objects, especially the ones that are very close to SymPy, return a set of dace.symbolic.symbol (which extends (but does not wrap) SymPy's symbol) and sometimes you even get a mixture of them.
So this is a big set of work.
| block_reach: Dict[int, Dict[ControlFlowBlock, OrderedSet[ControlFlowBlock]]]) -> Set[SDFGState]: | ||
| closure: Set[SDFGState] = OrderedSet() |
There was a problem hiding this comment.
| block_reach: Dict[int, Dict[ControlFlowBlock, OrderedSet[ControlFlowBlock]]]) -> Set[SDFGState]: | |
| closure: Set[SDFGState] = OrderedSet() | |
| block_reach: Dict[int, Dict[ControlFlowBlock, OrderedSet[ControlFlowBlock]]]) -> OrderedSet[SDFGState]: | |
| closure: OrderedSet[SDFGState] = OrderedSet() |
| """ | ||
| single_level_reachable: Dict[int, Dict[ControlFlowBlock, | ||
| Set[ControlFlowBlock]]] = defaultdict(lambda: defaultdict(set)) | ||
| OrderedSet[ControlFlowBlock]]] = defaultdict(lambda: defaultdict(set)) |
There was a problem hiding this comment.
| OrderedSet[ControlFlowBlock]]] = defaultdict(lambda: defaultdict(set)) | |
| OrderedSet[ControlFlowBlock]]] = defaultdict(lambda: defaultdict(OrderedSet)) |
| # By definition, data that is referenced by the conditions (branching condition, | ||
| # loop condition, ...) is not single use data, also remove that. | ||
| for cfr in sdfg.all_control_flow_regions(): | ||
| single_use_data.difference_update(cfr.used_symbols(all_symbols=True, with_contents=False)) |
There was a problem hiding this comment.
used_symbols() also returns a set.
| array_name = an.data | ||
| write_subsets = set(e.data.dst_subset for e in st.in_edges(an)) | ||
| write_subsets = OrderedSet(e.data.dst_subset for e in st.in_edges(an)) | ||
| wss = str(write_subsets) |
There was a problem hiding this comment.
I know outside of the scope of this PR, but serializing a set and then use it as keys does not make sense to me.
The initial idea was to reduce the size of the cache folder, by only storing the components that are needed. To this end the code generator was modified such that different versions of build folder could be generated. Currently there are only two versions: - `development`: Which is the old full version, i.e. everything in a single folder. - `production`: This is a reduced folder and only contains the libraries (stub and program library) as well the version. The implementation has two parts. First `generate_program_folder()` was modified such that it only generates the parts that are absolutely needed, such as source files and anything else would not be generated in the first place. Then `compile_and_configure()` was modified such that it would remove the parts that are no longer needed (an example would be the source files which are needed for compilation but not afterwards). The changes are backwards compatible. Thus, caches that were generated _before_ this PR will still work, but they should be phased out. The changes are also done in a way that it should be simple to add new modes later, if their need arises. In Addition: - Also fixes that `sdfg.view()` fails if there are external SDFGs ([727cfb1](spcl@727cfb1)) - `sdfg.generate_code()` no longer generates the source maps as a side effect and writes them to disc. Instead they are generated by the `generate_program_folder()` ([5e1694b](spcl@5e1694b)). - Dumping of the configuration in the build folder ([eb54062](spcl@eb54062)). - Miscellaneous refactoring in the `ReloadableDLL`, that changed its interface.
Old version is now deprecated
Unused imports add a performance overhead at runtime, and risk creating import cycles. To automatically detect and remove them in the future, this PR suggests to add [ruff](https://docs.astral.sh/ruff/) as a `pre-commit` hook to detect unused imports. I've added an exception for `__init__.py` files to allow re-exports without adding an explicit `__all__` list or adding extra annotations. The PR grew quite big. However, non-automatic changes are only in the following seven files - `.pre-commit-config.yaml`: configure `pre-commit` to run the `ruff` linter - `ruff.toml`: configure the `ruff` linter to only search for unused imports (rule `F401`) - `dace/autodiff/library/library.py`: make sure we keep the `ParameterArray` import for backwards compatibility - `dace/frontend/python/replacements/operators.py`: make sure we keep the `dace` import for evaluation of data types - `tests/library/include_test.py`: make sure we keep the necessary import in the middle of the test - `dace/sdfg/analysis/schedule_tree/treenodes.py`: manually remove the now trivial `if TYPE_CHECKING` branch
When we started to enforce consistent formatting on the CI (PR spcl#1957), an important discussion point was for developers to see changes that needed to be applied. For lack of better knowledge, I've added a small script to show the `git diff` output in case of failure. I recently learned that `pre-commit` has a built-in `--show-diff-on-failure` option <img width="3241" height="1202" alt="image" src="https://github.com/user-attachments/assets/843ebae8-1dda-401d-913e-f6dabf150bd0" /> which enables exactly this behavior out of the box ([link to failing workflow run](https://github.com/spcl/dace/actions/runs/24556723300/job/71794961944?pr=2337)). It even comes with a colored output ... 🤩 I suggest, we drop the custom script since this option is much simpler.
- Fix script to query the HIP architecture of the machine - Remove explicit setting of `--offload-arch` in `HIP_HIPCC_FLAGS` - Set `CMAKE_HIP_FLAGS` instead of `EXTRA_HIP_FLAGS`
When we build an SDFG, there's the option to store `DebugInfo` with some SDFG nodes. For example, this `DebugInfo` can be used to store file & line information of parsed code when building an SDFG. When using the SDFG API, the default is to inspect the python stack and extract file & line information from there. These calls to `inspect` can/will be expensive, especially for bigger graphs. This PR proposes to add a configuration option, `compiler.lineinfo`, to drive this behavior from a single place. The defaults are kept as is, i.e. we keep inspecting the stack by default. However, the config option allows a central pace to turn `DebugInfo` off, which could be configured in production scenarios.
Two changes: - Rename `build_folder_version` to `build_folder_mode`. - Write folder mode to file `FOLDER_MODE` instead of `VERSION`. - Always save the `CACHEDIR.TAG` file .
## Description In PR spcl#2321 we introduced a way to turn of automatic stack inspection when adding a node to a state without explicitly stating debug info (such as file name, line info, ...). Since merging this PR, we get a ton of `DeprecationWarning`s from code added in `_get_debug_info()`. In a first attempt (see PR spcl#2344), we tried to work around the issue by setting `._default_line_info` on the state. This method proved to be very verbose and unnecessarily complicates parsing, where we have a legitimate use-case for explicitly passing along an explicit `DebugInfo`. This PR suggests to keep the API of `state.add_*()` functions as before, partially reverting changes from PR spcl#2321. Doing so, we can drop the deprecation warnings because nothing changes.
## Description This PR updates the action `codecov/codecov-action` from `v5` to `v6`. This step is needed to support GHA runners based on node24. <img width="1655" height="428" alt="image" src="https://github.com/user-attachments/assets/0901bf1b-2285-4394-b005-4b64b855233e" /> The only change in the codecov action between `v5` and `v6` is to support GHA runners based on node24. The update should come without any issues. More information can be found [here](https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/).
This fixes 2 bugs in map fission. 1) Loop-dependent views are incorrectly hoisted. For example: The read `__tmp_726_11_r` depends on `A[i]` but it is considered as the subset if not present within the NSDFG. <img width="608" height="512" alt="Pasted image" src="https://github.com/user-attachments/assets/84c525ae-f0d2-4184-a592-0fd0c0141c40" /> After it becomes wrongly hoisted and the SDFG does not compile anymore: <img width="752" height="515" alt="Pasted image (2)" src="https://github.com/user-attachments/assets/02a70bc3-55d6-4067-893b-bc7b29babb76" /> 2) Second, if the step size is not equal to 1, then the array dimensions between the maps are generated according to the map trip count, but the access expression is generated only via the map iterator. I fix this by generating the access expression for intermediate arrays via `(i - begin)/step` such that the generated access expression is always correct , regardless of the map's shape.
Fixes readthedocs failing build, as well as all errors and warnings in documentation, including adding a new Libraries and Environments page.
A simple pass that can detect some reduction patterns: From this: <img width="452" height="571" alt="image" src="https://github.com/user-attachments/assets/8a08db65-92e6-4193-abb8-4dc4a67a1b4b" /> to: <img width="580" height="460" alt="image" src="https://github.com/user-attachments/assets/2c7619d8-ee27-464e-a334-6a021673f6b7" /> or from: <img width="227" height="673" alt="image" src="https://github.com/user-attachments/assets/73bbc634-e3b9-4474-812f-6ea2573042ee" /> to: <img width="262" height="663" alt="image" src="https://github.com/user-attachments/assets/9127ab4d-365a-400f-b5e7-6556de9016b2" />
In [PR#2331](spcl#2331) and [PR#2348](spcl#2348) the possibility of different folder modes/versions was introduced with the goal of reducing the size of the generated folder. The idea was to "only dump what is needed". In `production` `program.sdfgz` (serialized SDFG) was not dumped. This had the effect that it is not possible to construct a `CompiledSDFG` object (and call the SDFG from Python) without having the source SDFG in the first place. This PR solves this issue by again dumping the SDFG. Note in the long run one should switch to `nanobind` instead of `ctype`, which would make the binary an importable package and would also allow to offload the processing of the arguments to `C++`. For more information on this see [issue#2362](spcl#2362).
…ic (spcl#2372) ## Maps must have a positive step A DaCe `Map` is an unordered, ascending iteration domain, and the CPU backend emits an ascending loop only (`for (i = begin; i < end + 1; i += step)`). A negative/descending step silently miscompiles. As we would need to check for `>`. I think Loop2Map already normalizes the loops, but nothing prevents the user from writing `dace.map[10:0:-1]` (validation passes) which I did and noticed this bug :). a ### Changes - **`Map.validate`**: reject a *provably* descending map. - **CPU codegen**: for a symbolic step whose sign can't be decided statically, emit a debug-only `assert((step) > 0)` before the OpenMP pragma. It should not have a performance impact, as we compile with `-O3` by def. No existing test uses a negative-step `dace.map`; positive-step maps are unchanged.
Convert process grids, subarrays, and redistribution arrays to special data descriptors. Removes specialized SDFG fields and streamlines code generation.
Small fix for sporadic test failure caused by low floating point tolerance and unseeded random input.
- [x] Clarify interstate edge assignment type - [x] Make pystr_to_symbolic more efficient by using globals - [x] Create a `Subscript` function in symbolic expressions --------- Co-authored-by: Yakup Koray Budanaz <budanaz.yakup@gmail.com>
Instead of symbolic expressions as sympy strings (which might be ambiguous in symbol names, assumptions, and properties), this PR modifies symbol serialization to emit strings as follows: * Symbols are always prefixed with a dollar sign (`$i`, `$return`). * Symbols with non-default assumptions are saved with a unique function called `symbol`. For example, `symbol($symbol, integer=True)` or `symbol($N, dtype=dace.uint64)`. * All constants follow a Rust-like convention that suffixes the data type at the end. For example: `1i16, 7.2bf16, 8.0f64, 9f32` and will always deserialize to the same type in code generation. * The `SymExpr` class is serialized as a function `SymExpr(expr, overapprox)`. * No implicit simplification done on the expressions. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tbennun <8348955+tbennun@users.noreply.github.com> Co-authored-by: Tal Ben-Nun <tbennun@gmail.com> Co-authored-by: Tal Ben-Nun <tbennun@users.noreply.github.com> Co-authored-by: Yakup Koray Budanaz <budanaz.yakup@gmail.com> Co-authored-by: Yakup Koray Budanaz <ybudanaz@ethz.ch>
## Problem
`ProgramVisitor.make_slice` builds the slice-read memlet with
`Memlet.simple(array, rng, ...)`. `Memlet.simple` stores a passed-in
`Subset` **by reference** (it does not copy), and `rng` is frequently
the cached `Range` object handed back by the `accesses` cache on a
repeated read of the same array slice.
So two sibling reads of the same slice (e.g. `arr[i, k]` in two loop
bodies under a map) produce two distinct edges whose memlets **share one
subset object**. That violates the invariant that each memlet owns its
subset: any later in-place subset rewrite — loop-iterator renaming,
symbol replacement, offsetting — on one edge silently corrupts the
other.
### Reproducer
```python
N = dace.symbol('N')
@dace.program
def two_sibling_slice_reads(out: dace.float64[N, N], arr: dace.float64[N, N]):
for i in dace.map[0:N]:
for k in range(N):
out[i, k] = arr[i, k]
for k in range(N):
out[i, k] += arr[i, k]
```
In the parsed SDFG, the two `arr[i, k]` read edges share a single
`Range` subset object. A pass that rewrites a subset in place on one of
them (the new test originally surfaced this through a loop-iterator
rename) then corrupts the sibling.
## Fix
Deepcopy `rng` for the slice memlet's subset in `make_slice`, mirroring
the `other_subset` deepcopy two lines above. This is a pure
object-identity fix — subset values and the generated SDFG are
unchanged.
## Test
`tests/python_frontend/slice_subset_aliasing_test.py` asserts that no
two memlets in the parsed SDFG share a subset object, plus an end-to-end
value check. Fails before the fix, passes after.
This PR allows SDFGs from versions earlier than 2.0.0a4 (pre-released with this PR) to be loaded successfully.
[PR#2366](spcl#2366) introduced a new global that was used during serialization. However, this fails when multiple threads are active, which is not the default mode of operation (except in GT4Py and the [DaCe tests themselves](spcl#2126)). This PR made the serialization thread safe. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…line (spcl#2401) This PR uses a list for the return type of `depends_on()` in order to enforce deterministic dependency ordering in SDFG optimization pipelines. --------- Co-authored-by: Till Ehrengruber <till.ehrengruber@cscs.ch>
This PR adds a special case for 1D slices that are contiguous in that dimension, but are not packed. --------- Co-authored-by: Yakup Koray Budanaz <budanaz.yakup@gmail.com>
## Description This PR updates `actions/checkout` from `v6` to the latest `v7`. No breakage is expected from reading the change log.
## Summary
`dace.symbolic.simplify` is memoized with `functools.lru_cache`.
`lru_cache`
keys its entries by `hash`/`==`, and in Python booleans *are* integers:
```python
>>> hash(True) == hash(1)
True
>>> True == sympy.Integer(1)
True
```
As a result the cache cannot tell `simplify(True)` apart from
`simplify(sympy.Integer(1))` — they collapse onto a single entry.
Whichever is
evaluated first wins, and the other caller gets the wrong-typed result:
```python
>>> from dace.symbolic import simplify
>>> simplify(True) # caches sympy.true under a key equal to 1
True
>>> simplify(sympy.Integer(1)) # cache hit -> returns the *boolean*
True # expected: 1 (a sympy.Integer)
>>> type(simplify(sympy.Integer(1)))
<class 'sympy.logic.boolalg.BooleanTrue'>
```
The same conflation applies to `False` / `Integer(0)`.
## Impact
The corruption is **process-global and order dependent**: any code path
that
feeds a plain Python `bool` to `simplify` (for example a comparison like
`simplify(s == 1)`, which evaluates to `True`/`False` before reaching
`simplify`) poisons the cache for every later
`simplify(sympy.Integer(1))` /
`simplify(sympy.Integer(0))`.
Downstream, this surfaces as a hard crash in memlet-volume propagation.
For a
single-iteration map the propagated volume is `sympy.Integer(1)`, so
`propagate_subset` (`dace/sdfg/propagation.py`) calls
`simplify(Integer(1))`,
gets back `BooleanTrue`, and the `Memlet.volume` setter rejects it:
```
TypeError: Property volume must be a literal or symbolic expression,
got: <class 'sympy.logic.boolalg.BooleanTrue'>
```
Because it depends on evaluation order across the whole process, the
failure is
intermittent and hard to attribute — an unrelated SDFG breaks long after
the
`bool` was simplified.
## Fix
Construct the cache with `typed=True` so arguments of different types
are cached
under distinct entries, even when they hash and compare equal:
```diff
-@lru_cache(maxsize=2048)
+@lru_cache(maxsize=2048, typed=True)
def simplify(expr: SymbolicType) -> SymbolicType:
return sympy.simplify(expr)
```
With `typed=True`, `simplify(True)` (a `bool`) and
`simplify(sympy.Integer(1))`
(a `sympy.Integer`) occupy separate cache slots, so neither can return
the
other's result.
## Testing
New regression test: `tests/simplify_cache_typed_test.py`. It covers:
- `simplify(True)` then `simplify(Integer(1))` (and the reverse) stay
correctly
typed — both directions, for `True`/`1` and `False`/`0`.
- Normal symbolic expressions still simplify and are still served from
the cache
(`cache_info().hits`).
- End-to-end: after a `bool` is passed to `simplify`, memlet-volume
propagation
over a single-iteration map still succeeds (this reproduced the original
`TypeError` verbatim before the fix).
---------
Co-authored-by: Yakup Koray Budanaz <ybudanaz@ethz.ch>
## Description `dace/math.h` re-exposes a couple of math functions from `cmath`, e.g. `std::sin(x)`, which has overloads for e.g. `float` and `double`. Some functions, e.g. `asin(x)`, were not exposed in this way. This leads to precision issues when `asin(x)` is called with a `float` because in the generated code, `asin` will be mapped to [the C version](https://en.cppreference.com/c/numeric/math/asin), which is only defined for doubles. There is thus an implicit cast happening of the argument and the computation is done in double precision. Worse, the return type is always a `double`, which will force the rest of the calculation to be up-casted to double precision if `asin()` is used in an expression. This is a re-hash (including the missing test) of spcl#2364, which grew way too big and is now plucked apart into smaller, more meaningful PRs. @alexnick83 would you mind giving this another look? I've added a simple test for `asin()` as you asked in spcl#2364 (review).
…ee nodes (spcl#2419) ## Description Every tree node has functions to calculate input and output memlets of that node. Tree scopes have a default implementation to gather all inputs/outputs of their children. That default implementation for scopes didn't consider read after write (within the same scope). This caused "too many inputs" to be returned, which - in turn - caused the dependency analysis of the state boundary inserter to generate wrong results, which - in the end - generated wrong SDFGs from a given schedule tree. This PR changes the default implementation of `_gather_memlets_in_scope()` to account for read-after-write memlets and adds a test. This PR is part of a series of commits to bring back the fixes from the June push (i.e. spcl#2364) into `main` as clean commits with test cases.
PyTorch 2.13 removed Named Tensors (pytorch/pytorch#173895)
Data descriptor validation rejected NumPy integer scalars in shapes, strides, and offsets despite their integral semantics. - **Validation** - Accept `numbers.Integral` implementations, including NumPy integer types. - Preserve existing symbolic expression support. - **Coverage** - Add regression coverage for NumPy-backed shape, stride, and offset values. ```python dace.float64[np.int32(10)] ``` --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tbennun <8348955+tbennun@users.noreply.github.com> Co-authored-by: Tal Ben-Nun <tbennun@users.noreply.github.com>
## Description This is the last commit in cleaning up "the NASA working branch", i.e. spcl#2364. It contains a couple of smaller unrelated (type) fixes and typos. It is assumed that all of these changes are covered (in one way or another) by existing test cases.
## Description This PR brings back a couple of stree -> SDFG fixes from the NASA working branch spcl#2364, which accumulated a bunch of changes last month. In particular, this PR contains 1. consider scalar memlets e.g. when collecting memlets of conditions. our use-case is a conditional inside a triple-nested loop where the conditional depends on a scalar condition that is calculated outside the loop (see test case). 2. connect all source / sink nodes inside a map with empty memlets to the corresponding entry / exit node of the containing map (e.g. see image below of what was wrong before) 3. proper support for (self-assigning) copy nodes 4. ensure unique names of added loop regions (this is enforced by validation) Support for collecting scalar memlets bleeds out into `sdfg.py` and `state.py`. All changes there (outside the schedule tree world) are fully backwards compatible. --- problematic stree -> sdfg transformation before fix number 2 <img width="1920" height="1001" alt="image" src="https://github.com/user-attachments/assets/bc798341-bd84-4951-b80f-eed77fbaa653" /> --------- Co-authored-by: Tal Ben-Nun <tbennun@users.noreply.github.com>
## Description Since PR spcl#1678 merged, the docstring of `SDFG.read_and_write_sets()` is outdated because the behavior of `SDFGState._read_and_write_sets()` changed. Read after write situations are no longer considered as explained in the the added NOTE in `SDFGState._read_and_write_sets()`: https://github.com/spcl/dace/blob/bcb0257f89b0c110330da43eee9e3ed77163b92a/dace/sdfg/state.py#L803-L807 This PR suggests to change the docstring of `SDFG.read_and_write_sets()` to avoid wrong expectations of users. /cc @philip-paul-mueller
## Description Quick follow-up from spcl#2418. `std::atan2` exposed as `dace.math.atan2` computes the arc tangent of `y / x` and thus requires two arguments.
…terministic # Conflicts: # dace/sdfg/state.py # dace/transformation/helpers.py
Just a basis for further discussions.