FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

api: fix staggered evaluation for add/mul by mloubout · Pull Request #2782 · devitocodes/devito · GitHub

api: fix staggered evaluation for add/mul - #2782

Merged
mloubout merged 8 commits into
mainfrom
fd-eval-add
Dec 8, 2025
Merged

api: fix staggered evaluation for add/mul#2782
mloubout merged 8 commits into
mainfrom
fd-eval-add

Conversation

Copy link
Copy Markdown
Contributor

No description provided.

mloubout added the API api (symbolics, types, ...) label Oct 31, 2025

codecov Bot commented Oct 31, 2025
edited
Loading

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.02740% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.01%. Comparing base (d88639b) to head (26490d4).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
devito/arch/archinfo.py 18.75% 13 Missing ⚠️
devito/passes/clusters/implicit.py 0.00% 10 Missing ⚠️
devito/types/dense.py 71.42% 3 Missing and 1 partial ⚠️
devito/finite_differences/differentiable.py 72.72% 2 Missing and 1 partial ⚠️
devito/passes/iet/misc.py 78.57% 3 Missing ⚠️
devito/passes/iet/parpragma.py 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2782      +/-   ##
==========================================
- Coverage   83.03%   83.01%   -0.03%     
==========================================
  Files         248      248              
  Lines       50898    50957      +59     
  Branches     4485     4489       +4     
==========================================
+ Hits        42265    42303      +38     
- Misses       7865     7883      +18     
- Partials      768      771       +3     
Flag Coverage Δ
pytest-gpu-aomp-amdgpuX 68.49% <54.12%> (-0.05%) ⬇️
pytest-gpu-nvc-nvidiaX 69.02% <54.12%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

mloubout force-pushed the fd-eval-add branch 2 times, most recently from 766cc81 to a156d3c Compare October 31, 2025 16:05

Copy link
Copy Markdown

Check out this pull request on 

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

mloubout force-pushed the fd-eval-add branch 2 times, most recently from 1a9951a to dc0e8f5 Compare November 3, 2025 13:01

FabioLuporini left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Uncontroversial

mloubout force-pushed the fd-eval-add branch 7 times, most recently from 42588ae to 6c6d45f Compare November 4, 2025 04:17

JDBetteridge left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

A lot of the norms in the tests and notebooks have changed significantly, how do we know these are now the "right values". And is the test really valuable if we do not know what the true value is?

One could suggest that these are in some sense "regression tests", but then we definitely should not be changing the values!

mloubout commented Nov 6, 2025

Copy link
Copy Markdown
Contributor Author

A lot of the norms in the tests and notebooks have changed significantly

The one norm that changed a lot in test_mpi.py .... was indeed because of a bug introduced, made me find it so thanks, the rest is small changes due to a few extra interpolation but within tolerance

@@ -952,6 +952,11 @@ def _evaluate(self, **kwargs):
class DiffDerivative(IndexDerivative, DifferentiableOp):
pass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

drop pass now

class DiffDerivative(IndexDerivative, DifferentiableOp):
pass

def _eval_at(self, func):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

isn't it the case of IndexDerivative too?

kwargs.pop('is_commutative', None)
return self.func(*args, **kwargs)

def _eval_at(self, func):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

nitpicking: Perhaps could be moved into a mixin class from which all *Derivative class inherit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Seconded: EvaluatedMixin perhaps?

Comment thread devito/types/basic.py Outdated
# Two indices are aligned if they differ by an Integer*spacing.
v = (i - j)/d.spacing
if not i.has(d):
# Maybe a subdimension

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

nitpicking, SubDimension

Comment thread devito/types/basic.py
v = (i - j)/d.spacing
if not i.has(d):
# Maybe a subdimension
dims = {sd for sd in i.free_symbols if getattr(sd, 'is_Dimension', False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

isinstance

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

avoids circular (so local) import using getattr

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

it's just a hack to avoid a hack in that case?

anyway, u just reminded me that unless i is a Number or a Dimension w/o an offset, it will be of type AffineIndexAccessFunction https://github.com/devitocodes/devito/blob/main/devito/types/dimension.py#L1822

so all you have to here is

try:
    sds, = i.sds
except (AttributeError, ValueError):
    # Expected exactly one StencilDimension
    continue

btw should we not raise a NotImplementedError if len(sds) > 1 instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Don't think it's better. You have to check all three of i.sds, i.d, o.ofs and see if one is a sbdimensions it doesn't make it cleaner or simpler.

btw should we not raise a NotImplementedError if len(sds) > 1 instead?

No if there is multiple dimensions it's considered that the user decided to specifiy its own indices and to be valid ones. Would lead to a lot of breakign cases to raise and error here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Could you use is_Sub?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Could you use is_Sub?
No because it could be something other than a Dimension

Comment thread devito/types/basic.py
for the compiler.
"""
mapper = self._grid_map
subs = mapper.pop('subs', {})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

since _grid_map is (must be) a cached_property, use .get() instead of .pop()

In fact, _grid_map, now that you make me think twice about it, should return a frozendict to be pedantic

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality
  • I should not be cached no, that would break all of the FD since f and f._subs(x, x+.5) would end up with the same _grid_map when the second needs interpolation
  • using pop so that can just iterate mapper below

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

oh that's because we still have that crazy hack that makes every instance of f(x,y,z) reference the same __dict__ as the "origin" Function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

No this is because this is based on indices. Every f.subs is the same f with different indices (and has too or it would get really nasty with data/.....) and this checks if those specific indices are on the grid given the reference index (staggered "dim")

Comment thread devito/types/basic.py Outdated
# Evaluate. Since we used `self.function` it will be on the grid when
# evaluate is called again within FD
retval = retval._evaluate(**kwargs)
if subs:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I don't think this if is needed, .subs should take O(1) when subs is empty by returning immediately

Comment thread devito/types/dense.py Outdated
except KeyError:
pass

if mapper:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

nitpicking, but same as before. We can reduce verbosity by avoiding these if since they practically speaking won't improve performance

Comment thread devito/types/dense.py Outdated

if mapper:
return self.subs(mapper)
return self

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

nitpicking, perhaps a blank line

Comment thread tests/test_derivatives.py
eqne = eqn.evaluate.rhs
assert simplify(eqne - (p._subs(y, yp).evaluate * f).dx(x0=xp).evaluate) == 0

def test_param_stagg_add(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

quite a few changes have been made, and it seems weird that only one new test was required?

were they all fixing the same exact (set of) thing(s), which is entirely captured by this test? if so, OK

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This is the only "change" that is tested. Everything else isadjustments from removing is_Parameter and using subs in eval_for_fd instead of explicit derivatives

JDBetteridge left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Just flagging a couple of other big changes that might need investigation (or given they are just in the notebooks, maybe re-running after your bug fix)

"source": [
"from devito import norm\n",
"assert np.isclose(norm(u), norm(U[0]), rtol=1e-5, atol=0)"
"assert np.isclose(norm(u), norm(U[0]), rtol=1e-2, atol=0)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This is 3 orders of magnitude change in the difference between two values that are notionally the same

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

The numpy implementation is techinically wrong, as it does staggering but does not interpolate u/v where needed so there is some difference now because forcing the devito equation to be the same would be tricky since "incorrect"

"outputs": [],
"source": [
"assert np.isclose(np.linalg.norm(rec.data), 4263.511, atol=0, rtol=1e-4)"
"assert np.isclose(np.linalg.norm(rec.data), 3640.584, atol=0, rtol=1e-4)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This jump is also pretty big!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Likely some parameters interpolation that were not being processed due to the SubDimensions. Will double check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Yes, basically comes from parameters (damp, ro, ...) being properly interpolated now.

mloubout force-pushed the fd-eval-add branch 5 times, most recently from cfcadc9 to ef1f7d8 Compare November 12, 2025 15:06
mloubout force-pushed the fd-eval-add branch 4 times, most recently from 01d3d1a to cea48f3 Compare November 18, 2025 02:08
mloubout force-pushed the fd-eval-add branch 7 times, most recently from 0823c5f to 7821fc1 Compare November 21, 2025 14:48
mloubout force-pushed the fd-eval-add branch 3 times, most recently from 3cd6f39 to f91130f Compare November 26, 2025 14:06
def key(i):
try:
return i.indices[d]
except (KeyError, AttributeError):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

why? and what triggers this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Same as above

edims = set(retrieve_dimensions(tkn, deep=True))
return dim._defines & edims and edims.issubset(prefix.dimensions)

mapper = {k: v for k, v in mapper.items() if key(v)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

what triggered this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Current version is too simplistic and misses things like combination of implicit dims. Those changes here are preleminary to the topography multisubdomain when for example taken in intersection of an abox where you end up with multimple implicit dims in the tkn.

Comment thread devito/passes/iet/misc.py
except (AttributeError, ValueError):
dtype = np.float32
eps = np.finfo(dtype).resolution**2
b = printer()._print(Cast('b', dtype=dtype))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

can use a blank line before this line

also, instantiating the printer at this point is quite weird. Even more if you think one new instance will be instantiated each time you enter this function. Why don't we create the printer once and for all at the beginning of lowering or wherever (as soon as ...) possible?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

That's how the printer is used throughout the codebase. It's instantiated when needed.

Comment thread devito/passes/iet/misc.py
Comment thread devito/types/basic.py Outdated
retval = self.subs({i.subs(subs): self.indices_ref[d]
for d, i in mapper.items()})
if 'harmonic' in self._avg_mode:
retval = retval.safe_inv(retval, safe='safe' in self._avg_mode)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

safe='safe' is a bit clumsy

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

It's a boolean 'safe' in self._avg_mode not safe='safe'

Comment thread devito/types/basic.py Outdated
if self._avg_mode == 'harmonic':
from devito.finite_differences.differentiable import SafeInv
retval = SafeInv(retval, self.function)
if 'harmonic' in self._avg_mode:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

nitpicking: the _avg_mode should be an instance of something rather than an iterable of string, so that you may do e.g. self._avg_mode.is_harmonic or self._avg_mode.is_harmonic_safe

Comment thread devito/types/dense.py
return self.subs(mapper)
return self

mapper = {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

aka

m0 = self.indices_ref
m1 = func.indices_ref

mapper = {m[d]: m1[d] for d in self.dimensions if m0.get(d) is not m1.get(d)}

?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

No that would still throw a KeyError when d is in m0 and not m1 as m0.get(d) is not m1.get(d) would be True but m1[d] doesn't exist

Comment thread devito/types/dense.py Outdated
retval = super()._evaluate(**kwargs)
if not self._time_buffering and not retval.is_Function:
# Saved TimeFunction might need streaming, expand interpolations
# for easier processing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

(ultra nitpick) no full stop at the end of comments

Comment thread devito/types/dense.py
def _evaluate(self, **kwargs):
retval = super()._evaluate(**kwargs)
if not self._time_buffering and not retval.is_Function:
# Saved TimeFunction might need streaming, expand interpolations

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

why the retval.is_Function part? what else can it be?

mloubout Dec 2, 2025
edited
Loading

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Derivative when there is an interp. But if it's a Function then you get an infinite recursion on .evaluate

mloubout force-pushed the fd-eval-add branch 3 times, most recently from b1f1e14 to edd10f2 Compare December 4, 2025 19:44
Comment thread devito/arch/archinfo.py
lib = ctypes.CDLL(libname)

cudaDevAttrMaxSharedMemoryPerBlockOptin = 97
# get current device

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Ultra nitpick: Comment doesn't start with a capital

Comment thread devito/arch/archinfo.py
return 64 * 1024 # 64 KB default
lib = ctypes.CDLL(libname)

cudaDevAttrMaxSharedMemoryPerBlockOptin = 97

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Maybe abbreviate this name and add a comment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

That's the actual CUDA name

kwargs.pop('is_commutative', None)
return self.func(*args, **kwargs)

def _eval_at(self, func):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Seconded: EvaluatedMixin perhaps?

Comment thread devito/types/basic.py
if self._avg_mode not in ['arithmetic', 'harmonic']:
if self._avg_mode not in ['arithmetic', 'harmonic', 'safe_harmonic']:
raise ValueError("Invalid averaging mode_mode %s, accepted values are"
" arithmetic or harmonic" % self._avg_mode)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Does this error need updating?

Comment thread devito/types/basic.py
v = (i - j)/d.spacing
if not i.has(d):
# Maybe a subdimension
dims = {sd for sd in i.free_symbols if getattr(sd, 'is_Dimension', False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Could you use is_Sub?

mloubout merged commit a00cca8 into main Dec 8, 2025
39 checks passed
mloubout deleted the fd-eval-add branch December 8, 2025 17:37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API api (symbolics, types, ...)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants


Back | FazBrowse Home | New Git URL