Skip to content

fix[next]: empty range is absorbing for domain intersection#2702

Merged
tehrengruber merged 3 commits into
GridTools:mainfrom
havogt:fix-empty-domain-intersection
Jul 14, 2026
Merged

fix[next]: empty range is absorbing for domain intersection#2702
tehrengruber merged 3 commits into
GridTools:mainfrom
havogt:fix-empty-domain-intersection

Conversation

@havogt

@havogt havogt commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

_reduce_ranges in domain_utils.py is shared by domain_union and domain_intersection:

_range_union        = functools.partial(_reduce_ranges, start_reduce_op=im.minimum, stop_reduce_op=im.maximum)
_range_intersection = functools.partial(_reduce_ranges, start_reduce_op=im.maximum, stop_reduce_op=im.minimum)

#2677 taught it to drop statically empty ranges before folding. That is correct for the union, where an empty range is the identity element, but wrong for the intersection, where an empty range is the absorbing element.

Dropping the empty range from an intersection returns the remaining range instead of staying empty:

[1, 1[  &  [0, 10[   ->  [0, 10[     # should be empty
[1, 1[  &  [0, inf[  ->  [0, inf[    # should be empty

The second form is reached in practice: promote_domain fills non-condition dimensions with [-inf, inf[, and _infer_concat_where intersects that with the target domain. If the target range is statically empty, the half-infinite range survives verbatim and an InfinityLiteral leaks into the inferred domain.

Symptoms

Both show up in ICON4Py with concat_where over a statically-known-empty vertical range (a Rayleigh damping layer of zero thickness, where the layer bound is bound as a compile-time static arg):

  • gtfn — the leaked InfinityLiteral reaches itir_to_gtfn_ir._make_domain:

    TypeError: 'BinaryExpr.lhs' must be <class '...gtfn_ir_common.Expr'>
    (got 'InfinityLiteral.POSITIVE' which is a <class '...iterator.ir.InfinityLiteral'>).
    
  • dacegtir_to_sdfg_concat_where calls domain_intersection(source_domain, output_domain) directly at lowering time, with finite bounds. No infinity, no crash: a scalar concat_where branch whose range is empty ([1, 1[) is widened to the full output domain ([1, 10[), so the constant branch is broadcast over the whole domain and silently overwrites the real values.

The dace case is a silent wrong-results bug, so it is worth backporting alongside the crash.

Fix

Split the two operations. The union keeps dropping empty ranges; the intersection returns the empty range directly. Emptiness remains decidable only for static ranges — non-static ranges may not be empty, as before.

Also restores the constant folding that #2677 skipped in the single-surviving-range case.

Testing

  • Two regression tests in test_domain_utils.py covering the finite and the half-infinite case. Both fail on main.
  • tests/next_tests/unit_tests/ A/B'd with and without the change: no new failures.
  • Verified against ICON4Py (Update to GT4Py 1.1.12 C2SM/icon4py#1350): the gtfn TypeError and the dace diff_multfac_n2w mismatch both disappear, and the previously-failing GAUSS3D driver integration test passes.

Follow-up on #2677, related to #2205. The symbolic (non-static) case is still left to #2673.

🤖 Generated with Claude Code

Hannes Vogt and others added 3 commits July 10, 2026 15:12
`_reduce_ranges` is shared by `domain_union` and `domain_intersection`.
GridTools#2677 taught it to drop statically empty ranges before folding. That is
correct for the union, where an empty range is the identity element, but
wrong for the intersection, where it is the absorbing element.

Dropping an empty range from an intersection returns the remaining range,
e.g. `[1, 1[ & [0, inf[` yields `[0, inf[` instead of staying empty. Since
`promote_domain` fills non-condition dimensions with `[-inf, inf[`, the
surviving range can be unbounded, so an `InfinityLiteral` leaks into the
inferred domain and reaches the backends.

Split the two operations: the union keeps dropping empty ranges, the
intersection returns the empty range directly. Also restore the constant
folding that GridTools#2677 skipped when a single range survived the reduction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cover both the finite case (`[1, 1[ & [0, 10[`), which silently produced a
non-empty domain, and the half-infinite case (`[1, 1[ & [0, inf[`), which
leaked an `InfinityLiteral` into the result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tehrengruber
tehrengruber marked this pull request as ready for review July 14, 2026 10:52
@tehrengruber
tehrengruber merged commit 59a5260 into GridTools:main Jul 14, 2026
24 checks passed
@havogt
havogt deleted the fix-empty-domain-intersection branch July 15, 2026 19:34
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.

2 participants