Repository navigation
Conversation
GuardFactorNe inherited CondEq through GuardFactor ahead of CondNe, so it was an equality and printed `==`. Negating a subsampling guard thus reproduced the guard itself, selecting the timesteps on the subsampled grid instead of those off it
A condition SymPy folds to `false`, e.g. `time < 0`, was printed as `if (false)`, which is not C99 without `stdbool.h` and failed to compile
A condition such as `time >= 0` folds to `true` (`time` is nonnegative)
and gave a Cluster the guard {time: true}, distinct from no guard.
Buffering then created one buffer per guard for equivalent accesses
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3061 +/- ##
=======================================
Coverage 84.23% 84.23%
=======================================
Files 258 258
Lines 57070 57125 +55
Branches 4874 4877 +3
=======================================
+ Hits 48072 48122 +50
- Misses 8170 8174 +4
- Partials 828 829 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| guards = {d: mode(*v, evaluate=False) for d, v in guards.items()} | ||
|
|
||
| # A condition folded to `true` (e.g., `time >= 0`) imposes no guard | ||
| guards = {d: v for d, v in guards.items() if v is not sympy.true} |
There was a problem hiding this comment.
I think this should rather be fixed directly in class Guards, within devito/ir/support/guards.py: a guard, along any Dimension, should never be set (being pointless) if its value reduces to sympy.true
There was a problem hiding this comment.
Done in 4c8c1cd: Guards.__init__ now drops any entry equal to sympy.true, so no entry point (constructor, andg, xandg, impose, subs, ...) can set one, and the filter in the guard pass is gone. Added TestGuards::test_true_never_set; the buffering test still covers the original case.
Drop such guards in `Guards` itself, at construction, rather than in the `guard` pass, so that no entry point (`andg`, `xandg`, `impose`, `subs`, ...) can set one
|
|
||
| ct = ConditionalDimension(name='ct', parent=time, factor=2) | ||
| cn = ConditionalDimension(name='cn', parent=time, | ||
| condition=Not(GuardFactor(ct)), relation='strict') |
There was a problem hiding this comment.
GuardFactor should not be used at this level this is an internal object, this needs to be a user API def, like Not(CondEq(...
There was a problem hiding this comment.
Fair point, fixed in 763b8b2. GuardFactor is only ever built by the compiler from a ConditionalDimension(factor=...), and its negation only arises in buffering, when a copy-back is overwritten by a later one guarded by the factor dimension — so the test now goes through that: two writes to the same usave slot, one at the last timestep and one under factor=2, noop vs buffering (test_buffering.py::test_negated_factor_guard). Fails on main with if (time == 7 && time%tsubf == 0), passes with != 0. The old test is gone from test_dimension.py.
The negated subsampling guard is reached through buffering, when a copy-back is overwritten by a later one under a factor-based ConditionalDimension, so test it there instead of constructing the internal GuardFactor
| assert np.allclose(h.data[0], 12.) | ||
|
|
||
|
|
||
| def test_negated_factor_guard(): |
There was a problem hiding this comment.
that's an abuse of the API,
- the
usaveisn't defined ontsubbut ontime - It saves two different values at the same steps which doesn't make sense.
There was a problem hiding this comment.
You're right, it was contrived. I checked it against real code (recipes, devitohorizon, the devito examples and the devitopro demos/tests): every snapshot schedule saves on the subsampled dimension with a single writer, and the only same-slot two-writer case is the last-step save in your test_multi_cond_same_slot, which negates the CondEq guard, not the factor one, and is unaffected by the GuardFactorNe change. So nothing real reaches a negated factor guard, and I've dropped both that change and the test (4d820ac). The PR is now just the true/false guard fixes.
A ConditionalDimension whose condition folds to true must yield no Conditional in the generated code, under any relation
No user program reaches a negated factor guard: real snapshot schedules save on the subsampled dimension with a single writer, and the same-slot last-step save negates a CondEq instead. The test needed a contrived program to get there, so drop both
Two guard bugs that buffering exposes, each with a test against
opt='noop':if (false)was emitted. A condition SymPy folds tofalse(e.g.time < 0) printedif (false), which is not C99 withoutstdbool.h: a compile error. True and false now print as1and0.{time: true}guards.time >= 0folds totrueand gave a Cluster a guard distinct from no guard, so buffering created one buffer per "guard" for equivalent accesses.Guardsnow never sets a guard that reduces totrue, whatever the entry point.Tests:
test_dimension.py::TestConditionalDimension::test_false_condition,test_buffering.py::test_true_condition_single_buffer,test_ir.py::TestGuards::test_true_never_set. Each fails without its source change.An earlier
GuardFactorNechange was dropped (4d820ac): no real program reaches a negated factor guard.First of three stacked PRs: this one, then #3062 (copy-back skip rule) and #3063 (stale reads and lost writes).
🤖 Generated with Claude Code