Skip to content

Deterministic dace toolchain draft#17

Open
tehrengruber wants to merge 67 commits into
branch_gt4py-next-integration_2026_02_12from
dace_toolchain_deterministic
Open

Deterministic dace toolchain draft#17
tehrengruber wants to merge 67 commits into
branch_gt4py-next-integration_2026_02_12from
dace_toolchain_deterministic

Conversation

@tehrengruber

Copy link
Copy Markdown

Just a basis for further discussions.

romanc and others added 12 commits February 12, 2026 10:12
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 philip-paul-mueller left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a first high level review of the changes.

Comment thread dace/sdfg/graph.py
self._dst = dst
self._data: T = data

def id(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread dace/sdfg/graph.py
@@ -215,7 +218,7 @@ def __getitem__(self, node: NodeT) -> Iterable[NodeT]:

def all_edges(self, *nodes: NodeT) -> Iterable[Edge[EdgeT]]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def all_edges(self, *nodes: NodeT) -> Iterable[Edge[EdgeT]]:
def all_edges(self, *nodes: NodeT) -> List[Edge[EdgeT]]:

Comment thread dace/sdfg/nodes.py
return current


# TODO: check if the edges need an id. If source and dest, are equal they should be interchangable right?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread dace/sdfg/nodes.py
"type": typestr,
"label": labelstr,
"attributes": dace.serialize.all_properties_to_json(self),
# TODO(tehrengruber): This id looks very similar to the ID I introduced.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

Comment thread dace/sdfg/sdfg.py
##############################

# TODO(tehrengruber): This could also be done using the counter.
def _find_new_name(self, name: str):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread dace/sdfg/state.py
"""
return set()

# TODO(tehrengruber): should we make this an ordered set?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +90 to +91
block_reach: Dict[int, Dict[ControlFlowBlock, OrderedSet[ControlFlowBlock]]]) -> Set[SDFGState]:
closure: Set[SDFGState] = OrderedSet()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

tehrengruber and others added 16 commits April 15, 2026 11:11
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.
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/).
… of unsupported types (spcl#2354)

Enhance the error message in parser when failing to understand a
Sequence of non supported object.

Closes: spcl#2332
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"
/>
philip-paul-mueller and others added 7 commits May 20, 2026 04:23
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.
ThrudPrimrose and others added 22 commits June 17, 2026 06:36
This PR allows SDFGs from versions earlier than 2.0.0a4 (pre-released
with this PR) to be loaded successfully.
pystrings that have Abs, Min and Max (capital start) are considered user
functions until deserialized, creating issues. (see: spcl#2391).

This Pr fixes that.
[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>
This PR is similar to [PR#2404](spcl#2404)
but adds `typed=True` to all `lru_cache`s (except the ones that do not
take an argument.
## 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.
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.