Skip to content

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

Description

@ggorman

Summary

Devito prints finite-difference weights into the generated C as ~9-significant-digit float literals, introducing a ~1.7e-10 relative coefficient error vs the exact rational weights. This is physically harmless for production runs, but it silently floors any fp64 cross-implementation / verification gate at ~1e-10 per step: the achievable Devito-vs-exact-reference agreement is set by the printed-literal truncation, not by the discretization or the port under test.

Observed on 4.8.23.dev101+gcf7c3f30a (and earlier 4.8.x).

Root cause

  1. devito/finite_differences/finite_difference.py:27:
    _PRECISION = 9
    and line 187:
    weights = [sympify(scale * w).evalf(_PRECISION) for w in weights]
    — the exact rational Taylor weights (e.g. 8/315, -205/72) are materialized as 9-dps sympy.Floats at construction.
  2. devito/ir/cgen/printer.py:328 _print_Float honors the Float's own _prec (dps = prec_to_dps(expr._prec)), so the C literal carries only those ~9 digits (e.g. 2.53968254e-2 for 8/315, -2.847222220 for -205/72).

Minimal reproduction

import numpy as np
from devito import Grid, Function, Eq, Operator

grid = Grid(shape=(51, 51), extent=(50., 50.), dtype=np.float64)
u = Function(name="u", grid=grid, space_order=8)
v = Function(name="v", grid=grid, space_order=8)
op = Operator([Eq(v, u.laplace)])
print("2.53968254e-2 in C:", "2.53968254e-2" in str(op.ccode))   # True — 8/315 truncated

# impulse response: measure the printed tap vs the exact rational
u.data[:] = 0.0; u.data[25, 25] = 1.0
op.apply()
h2 = (50./50.)**2
tap = v.data[25, 29] * h2          # the +-4 tap of the 8th-order Laplacian row
exact = -1.0/560.0                 # exact rational weight
print("relative tap error:", abs(tap - exact) / abs(exact))     # ~1.7e-10, not ~1e-16

Measured downstream: a 10-step fp64 leapfrog cross-check against an exact-coefficient numpy implementation drifts to 2.2e-10 (vs 1.6e-15 when the same stencil is expressed with exact coefficients).

Why it matters

In a verification corpus we swept 332 Devito-comparison gates and found 11 gates 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 of those gates is blind to real coefficient defects at the magnitude its tolerance implies. The pattern is invisible-by-construction: the gate passes, so nobody re-reads it.

Verified workarounds (either restores fp64)

  • Express the stencil as indexed accesses with sympy.Rational coefficients — the printer's _print_Rational emits exact p.0F/q.0F divisions (residual < 1e-14).
  • Raise _PRECISION (e.g. to 30): the emitted literals then round-trip fp64 exactly (byte-match at ~1e-16 measured).

Suggested fix

Either bump _PRECISION from 9 to 17 (the fp64 round-trip minimum; the literal is parsed to the nearest double, so ≥17 sig digits makes the truncation sub-ULP), or keep the weights exact (Rational) through lowering and let the printer decide the target-precision rendering. A 17-digit literal has no runtime cost; the only diff is codegen text.

Happy to PR the one-line _PRECISION = 17 change with a regression test (generated-C literal round-trips to the exact rational within 1 ULP) if that direction is acceptable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions