Skip to content

FD weights: 17-digit C literals + canonicalized Taylor weights (fixes #2976)#2977

Open
ggorman wants to merge 1 commit into
mainfrom
fd-precision-17
Open

FD weights: 17-digit C literals + canonicalized Taylor weights (fixes #2976)#2977
ggorman wants to merge 1 commit into
mainfrom
fd-precision-17

Conversation

@ggorman

@ggorman ggorman commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Fixes #2976.

What

  1. _PRECISION 9 → 17 (finite_differences/finite_difference.py). 17 significant digits is the minimum that round-trips an IEEE-754 double, so the C literal parses back to the exact rational weight (sub-ULP) instead of carrying a ~1.7e-10 relative truncation. No runtime cost — only the codegen text changes.

  2. Taylor-weight canonicalization. Raising the precision exposed a latent inconsistency the 9-digit rendering had been masking (this is what the old "avoid roundup errors" comment was living with): Taylor weights built through a float x0 shift (e.g. div(f, shift=.5)) accumulate ~1e-14 relative float error in the weight recursion, so the same stencil built through two routes compared unequal at 17 digits (0.041666666666666664 vs …667, caught by test_shifted_div). Taylor-computed weights are now canonicalized — nearby-rational recovery within 5e-14 relative via Rational(w).limit_denominator(1e12), then evalf(_PRECISION) — so identical stencils are identical expressions and every emitted literal is the correctly-rounded double of the exact rational. User-supplied weights (weights= / symbolic coefficients) are rendered exactly as before — canonicalization applies only to the computed Taylor path.

  3. Tests. test_derivatives.py now imports the library _PRECISION rather than pinning a local copy (so these exact-equality tests track the library value, like test_symbolic_coefficients.py already does); the four 9-digit string expectations are regenerated at 17 digits; and a new TestCoefficientPrecision class pins the invariant: every FD weight literal round-trips to its exact rational as a double (this class fails at _PRECISION = 9 — e.g. 8/3152.53968254e-2, 1.7e-10 off — and passes at 17).

Why it matters

In a downstream verification corpus we swept 332 Devito-vs-reference gates and found 11 (in 9 projects) whose tolerances had been silently loosened into the 1e-7..1e-9 range specifically to swallow this floor, several with in-code comments like "Devito's generated C rounds the rational stencil coefficients". Each such gate is blind to real coefficient defects at the magnitude its tolerance implies. Details and measurements in #2976.

Test status

Ran locally (macOS, fp64): tests/test_derivatives.py + tests/test_symbolic_coefficients.py — 409 passed; tests/test_unexpansion.py 25 passed; tests/test_caching.py 60 passed. A repo-wide grep found no other 9-digit literal expectations. Please run the full CI matrix — happy to iterate on anything it surfaces.

Note: to be reviewed and merged by the devito maintainers (@mloubout) — not by us.

…2976)

_PRECISION 9 -> 17: 17 significant digits is the minimum that round-trips
an IEEE-754 double, so the C literals parse back to the exact rational
weight (sub-ULP) instead of carrying a ~1.7e-10 relative truncation that
silently floors fp64 cross-implementation checks.

Raising the precision exposed a latent route-inconsistency the 9-digit
rendering had been masking: Taylor weights built through a float x0 shift
(e.g. div(f, shift=.5)) accumulate ~1e-14 relative float error, so the
same stencil built through two routes compared unequal at 17 digits.
Taylor-computed weights are now canonicalized (nearby-rational recovery
within 5e-14 relative, then evalf) so identical stencils are identical
expressions; user-supplied weights are rendered as-is.

Tests: test_derivatives now imports the library _PRECISION instead of a
local copy; 9-digit string expectations regenerated at 17 digits; new
TestCoefficientPrecision regression class (weight-literal fp64 round-trip
+ evaluated-derivative Float atoms round-trip to their exact rationals).
@ggorman
ggorman requested a review from mloubout July 21, 2026 10:10
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.48%. Comparing base (0aa772b) to head (2c5ba75).

Files with missing lines Patch % Lines
devito/finite_differences/finite_difference.py 77.77% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2977      +/-   ##
==========================================
- Coverage   83.54%   83.48%   -0.06%     
==========================================
  Files         257      257              
  Lines       53591    53623      +32     
  Branches     4585     4588       +3     
==========================================
- Hits        44770    44766       -4     
- Misses       8028     8053      +25     
- Partials      793      804      +11     
Flag Coverage Δ
pytest-gpu-aomp-amdgpuX 68.37% <44.44%> (-0.03%) ⬇️
pytest-gpu-gcc- 78.39% <88.88%> (-0.01%) ⬇️
pytest-gpu-icx- 78.35% <88.88%> (+0.02%) ⬆️
pytest-gpu-nvc-nvidiaX 69.03% <44.44%> (-0.03%) ⬇️

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

☔ View full report in Codecov by Harness.
📢 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.

r = sympy.Rational(a).limit_denominator(10**12)
except (TypeError, ValueError, ZeroDivisionError):
return a
if a == 0 or abs(float(r) - float(a)) <= 5e-14 * abs(float(a)):

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.

float isn't safe its value will depend on the compilation flags and platform of python. This should likely use np.isclose with proper atol/ftol and np.float64 instead of float

at ``_PRECISION`` so the emitted C literal round-trips the intended double
exactly.
"""
def _canon(a):

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.

why is this a local function?

non-rational user-supplied coefficients are value-preserved), then render
at ``_PRECISION`` so the emitted C literal round-trips the intended double
exactly.
"""

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.

this is quite unreadable honestly, no idea what it's trying to say.

# compare unequal; user-supplied weights are rendered as-is.
scale = dim.spacing**(-deriv_order) if scale else 1
weights = [sympify(scale * w).evalf(_PRECISION) for w in weights]
if computed_weights:

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.

This flag is quite ugly, would make more sense to just have the _canonicalize_weight directly part of the weight computation. This "coefficients == 'taylor'" also ignore custom rules and things like the drp mode

Comment thread tests/test_derivatives.py
from devito.warnings import DevitoWarning

_PRECISION = 9
from devito.finite_differences.finite_difference import _PRECISION # noqa

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.

No blank line, wrong place (goes before devito.types)

Comment thread tests/test_derivatives.py
])
def test_fd_weight_literal_roundtrips_fp64(self, p, q):
w = sympy.Rational(p, q)
emitted = float(sympy.sympify(w).evalf(_PRECISION))

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.

float isn't safe

Comment thread tests/test_derivatives.py
f"{p}/{q}: evalf(_PRECISION={_PRECISION}) -> {emitted!r} != {exact!r} "
f"(rel err {abs(emitted - exact) / abs(exact):.3e})")

def test_evaluated_derivative_floats_roundtrip_fp64(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.

Not sure what this tests, or why it's testing a roundtrip for fp64 with an fp32 grid/....

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.

FD weights truncated to 9 significant digits in generated C (_PRECISION=9) — floors fp64 cross-validation at ~1e-10

2 participants