Skip to content

ENH: Decouple itk::Math from vnl_math (constants and functions)#6427

Merged
hjmjohnson merged 14 commits into
InsightSoftwareConsortium:mainfrom
hjmjohnson:itk-math-vnl-decouple
Jun 11, 2026
Merged

ENH: Decouple itk::Math from vnl_math (constants and functions)#6427
hjmjohnson merged 14 commits into
InsightSoftwareConsortium:mainfrom
hjmjohnson:itk-math-vnl-decouple

Conversation

@hjmjohnson

@hjmjohnson hjmjohnson commented Jun 10, 2026

Copy link
Copy Markdown
Member

Fully decouples itk::Math from vnl_math, preempting the deprecation of vnl_math:: constants and functions in upstream vxl: constants become correctly-rounded literals (C++20 static_assert-verified against std::numbers), the 14 function aliases become native implementations, and ITK-proper's last deprecated-entity uses (vnl_huge_val, legacy itk::Math::abs spellings) are migrated.

What changed
  • itkMath.h constants: 24 OEIS-referenced double literals, token-identical in every translation unit regardless of -std level; a C++20-guarded static_assert block proves bit-identity to std::numbers (18 identities; sqrt2pi has no std::numbers constant and one_over_sqrt2pi no exact identity — inv_sqrtpi / sqrt2 double-rounds 1 ULP high).
  • itkMath.h functions: the 14 using vnl_math::<fn> aliases (sgn, sgn0, sqr, cube, squared_magnitude, remainder_truncated/floored, rnd*, floor, ceil, angle_*) replaced by native constexpr/inline implementations preserving the historical interface; rounding functions delegate to existing itk::Math::Round*/Floor/Ceil.
  • #include <vnl/vnl_math.h> in itkMath.h gated behind !ITK_FUTURE_LEGACY_REMOVE (kept transitionally for downstream vnl_math:: users).
  • sqrteps corrected to exactly-representable 0x1p-26 (vnl's decimal literal rounds 1 ULP above sqrt(eps)); documented in the ITKv6 migration guide.
  • Tree sweep: last two vnl_math::pi uses → itk::Math::pi; vnl_huge_val(0) in FEMRegistrationFilter → NumericTraits<Float>::max(); 63 itk::Math::abs call sites (25 files) → itk::Math::Absolute; vestigial vnl_math.h includes removed.
  • VNL_ModernizeNaming.py rewritten to migrate all deprecated vnl_math spellings (namespace + legacy globals) to C++17-compatible itk::Math/std:: targets; vnl_huge_val flagged for manual type-aware fixes.
ODR rationale (review feedback)

Definitions dispatched on __cpp_lib_math_constants would give a program mixing C++17- and C++20-compiled translation units two definitions of the same inline variable — and for one_over_sqrt2pi the two paths actually differed by 1 ULP. Defining every constant from its literal makes the definitions standard-invariant; std::numbers participates only in C++20-guarded static_asserts, which are not entities and cannot violate ODR. See #5823 for the general concern.

Verification
  • New itkMathVnlParityGTest.cxx (9 suites, all passing) asserts bit-for-bit equivalence of every native against its vnl_math:: counterpart: halfway-case rounding, floor/ceil, sign functions, powers, all squared_magnitude overloads, the remainder sign matrix, angle normalization including the fmod boundary guard, and the constants (sqrteps's intentional 1 ULP divergence asserted explicitly).
  • A maximum-effort multi-angle review confirmed the rounding natives initially narrowed the valid input domain vs vnl's std::lrint/std::floor/std::ceil; fixed to the vnl bodies (full int range). Three vnl-inherited defects are fixed in a separate commit (ENH: Harden itk::Math natives beyond vnl_math parity): signed-overflow UB in squared_magnitude, intermediate-overflow UB in integral remainder_floored, and an unreachable tail in angle_0_to_2pi.
  • Full local ninja build: exit 0; 63/63 ctests across touched modules (Math, NIFTI, Mesh, Statistics, MathematicalMorphology).
  • itkMath.h compiled -fsyntax-only under all four configurations: -std=c++17/c++20 × default/ITK_FUTURE_LEGACY_REMOVE.
  • FEM change compile-verified by direct template instantiation (FEM tests not registered in local build config).
  • Bit-equivalence audit of all 19 literal↔std::numbers identities: 18 EXACT; one_over_sqrt2pi expression mismatch (hence its exclusion).
  • Tree-wide audit: zero remaining uses of any vxl-deprecated entity in ITK proper.

@github-actions github-actions Bot added type:Enhancement Improvement of existing methods or implementation type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct area:Core Issues affecting the Core module area:Filtering Issues affecting the Filtering module area:IO Issues affecting the IO module labels Jun 10, 2026
@hjmjohnson

Copy link
Copy Markdown
Member Author

@greptileai review this draft before I make it official

@greptile-apps

This comment was marked as resolved.

Comment thread Modules/Core/Common/include/itkMath.h Outdated
Comment thread Modules/Core/Common/include/itkMath.h Outdated
@hjmjohnson hjmjohnson force-pushed the itk-math-vnl-decouple branch from c032227 to 34583f4 Compare June 10, 2026 07:36
@hjmjohnson

Copy link
Copy Markdown
Member Author

Thanks — the P1 MSVC guard issue is real and fixed in 34583f4 (folded into the ENH commit).

The <numbers> include guard now also tests _MSVC_LANG >= 202002L, so it fires under MSVC /std:c++20 even without /Zc:__cplusplus — matching the exact condition under which <limits> already defines __cpp_lib_math_constants. This closes the use-without-include path your flowchart identified. Verified: pre-commit run --all-files clean, ITKCommon1TestDriver builds, all 10 itkMath.* tests pass locally.

Re sqrteps: the literal 1.490116119384766e-08 is a byte-exact copy of vnl_math::sqrteps (vnl_math.h:126). This PR's goal is to decouple from vnl_math while preserving values exactly; tightening it to the 1-ULP-closer sqrt(eps) would be a behavior change, so it's intentionally left out of scope.

@greptileai review

hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Jun 10, 2026
From greptile review on PR InsightSoftwareConsortium#6427
(InsightSoftwareConsortium#6427 (comment)):

  sqrteps is 1 ULP above sqrt(eps). The exact value of
  sqrt(2^-52) = 2^-26 in decimal is 1.490116119384765625e-8; the
  literal 1.490116119384766e-08 is 3.75e-24 above that exact value,
  which exceeds the half-ULP threshold (~1.65e-24) and therefore
  rounds to 2^-26 + 2^-78 instead of 2^-26. Since 2^-26 is a
  power-of-two and exactly representable, the hex float 0x1p-26 is
  the simplest, unambiguous fix -- and consistent with the PR's
  stated goal of correctly-rounded constants.

Verified locally that std::sqrt(std::numeric_limits<double>::epsilon())
returns exactly 0x1p-26 (IEEE-754 sqrt is correctly rounded), so the new
value equals what C++26 constexpr std::sqrt(eps) would produce.
@github-actions github-actions Bot added the area:Documentation Issues affecting the Documentation module label Jun 10, 2026
@hjmjohnson hjmjohnson force-pushed the itk-math-vnl-decouple branch 3 times, most recently from b90b91d to 96fbc67 Compare June 10, 2026 12:49

@N-Dekker N-Dekker 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.

Before merging, I think possible ODR violations should be considered.

Comment thread Modules/Core/Common/include/itkMath.h
Comment thread Modules/Core/Common/include/itkMath.h Outdated
hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Jun 10, 2026
From greptile review on PR InsightSoftwareConsortium#6427
(InsightSoftwareConsortium#6427 (comment)):

  sqrteps is 1 ULP above sqrt(eps). The exact value of
  sqrt(2^-52) = 2^-26 in decimal is 1.490116119384765625e-8; the
  literal 1.490116119384766e-08 is 3.75e-24 above that exact value,
  which exceeds the half-ULP threshold (~1.65e-24) and therefore
  rounds to 2^-26 + 2^-78 instead of 2^-26. Since 2^-26 is a
  power-of-two and exactly representable, the hex float 0x1p-26 is
  the simplest, unambiguous fix -- and consistent with the PR's
  stated goal of correctly-rounded constants.

Verified locally that std::sqrt(std::numeric_limits<double>::epsilon())
returns exactly 0x1p-26 (IEEE-754 sqrt is correctly rounded), so the new
value equals what C++26 constexpr std::sqrt(eps) would produce.
@hjmjohnson hjmjohnson force-pushed the itk-math-vnl-decouple branch from 96fbc67 to 2905196 Compare June 10, 2026 18:03
hjmjohnson added a commit to hjmjohnson/ITK that referenced this pull request Jun 10, 2026
From greptile review on PR InsightSoftwareConsortium#6427
(InsightSoftwareConsortium#6427 (comment)):

  sqrteps is 1 ULP above sqrt(eps). The exact value of
  sqrt(2^-52) = 2^-26 in decimal is 1.490116119384765625e-8; the
  literal 1.490116119384766e-08 is 3.75e-24 above that exact value,
  which exceeds the half-ULP threshold (~1.65e-24) and therefore
  rounds to 2^-26 + 2^-78 instead of 2^-26. Since 2^-26 is a
  power-of-two and exactly representable, the hex float 0x1p-26 is
  the simplest, unambiguous fix -- and consistent with the PR's
  stated goal of correctly-rounded constants.

Verified locally that std::sqrt(std::numeric_limits<double>::epsilon())
returns exactly 0x1p-26 (IEEE-754 sqrt is correctly rounded), so the new
value equals what C++26 constexpr std::sqrt(eps) would produce.
@hjmjohnson hjmjohnson force-pushed the itk-math-vnl-decouple branch from 2905196 to e99b0c2 Compare June 10, 2026 19:10
Replace the vnl_math:: aliases with self-contained definitions: the
correctly-rounded double literals (OEIS-referenced in vnl_math.h),
identical under every C++ standard level. Under C++20, a static_assert
block proves each literal bit-identical to its std::numbers equivalent.

Defining from literals unconditionally keeps every translation unit's
definition token-identical regardless of -std level, so mixing C++17
and C++20 TUs in one program cannot yield differing itk::Math values
(ODR concern raised in review; see also issue InsightSoftwareConsortium#5823). Notably,
std::numbers::inv_sqrtpi / std::numbers::sqrt2 double-rounds 1 ULP away
from the correctly-rounded one_over_sqrt2pi literal, so definitions
dispatched on __cpp_lib_math_constants would have produced standard-
dependent values for that constant.

This decouples itk::Math from vnl_math, whose constants are slated for
deprecation and eventual removal in upstream vxl.
No VXL_VERSION macro is referenced in this header; the include and its
explanatory comment described an intent the code never enforced.
From greptile review on PR InsightSoftwareConsortium#6427
(InsightSoftwareConsortium#6427 (comment)):

  sqrteps is 1 ULP above sqrt(eps). The exact value of
  sqrt(2^-52) = 2^-26 in decimal is 1.490116119384765625e-8; the
  literal 1.490116119384766e-08 is 3.75e-24 above that exact value,
  which exceeds the half-ULP threshold (~1.65e-24) and therefore
  rounds to 2^-26 + 2^-78 instead of 2^-26. Since 2^-26 is a
  power-of-two and exactly representable, the hex float 0x1p-26 is
  the simplest, unambiguous fix -- and consistent with the PR's
  stated goal of correctly-rounded constants.

Verified locally that std::sqrt(std::numeric_limits<double>::epsilon())
returns exactly 0x1p-26 (IEEE-754 sqrt is correctly rounded), so the new
value equals what C++26 constexpr std::sqrt(eps) would produce.
Downstream survey: ANTs uses itk::Math::sqrteps once, as an rcond()
tolerance; MITK uses vnl_math::sqrteps directly for plane-distance and
level-window tolerances. Neither is meaningfully affected by the 1 ULP
decrease to the exactly-representable 2^-26.
@hjmjohnson hjmjohnson force-pushed the itk-math-vnl-decouple branch from e99b0c2 to 589d680 Compare June 10, 2026 19:18
@hjmjohnson

Copy link
Copy Markdown
Member Author

@N-Dekker the ODR concern is addressed in 62ba138: constant definitions are now standard-invariant literals (no __cpp_lib_math_constants dispatch), with std::numbers used only in a C++20-guarded static_assert block. The audit it forced also caught a real 1 ULP divergence in the previous C++20 path for one_over_sqrt2pi — details in the inline reply. Please take another look when convenient.

@hjmjohnson hjmjohnson changed the title ENH: Decouple itk::Math constants from vnl_math (literals + C++20 std::numbers) ENH: Decouple itk::Math constants from vnl_math (literals, std::numbers-verified) Jun 10, 2026
@github-actions github-actions Bot added type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots area:Python wrapping Python bindings for a class area:Registration Issues affecting the Registration module area:Segmentation Issues affecting the Segmentation module labels Jun 10, 2026
@hjmjohnson hjmjohnson changed the title ENH: Decouple itk::Math constants from vnl_math (literals, std::numbers-verified) ENH: Decouple itk::Math from vnl_math (constants and functions) Jun 10, 2026
@hjmjohnson hjmjohnson marked this pull request as ready for review June 10, 2026 21:42
…tations

Replace the 14 using vnl_math::<function> declarations with native
definitions preserving the historical interface: constexpr sgn, sgn0,
sqr, cube, squared_magnitude, remainder_truncated, remainder_floored;
rnd, rnd_halfinttoeven, rnd_halfintup, floor, ceil with the vnl
std::lrint / std::floor / std::ceil bodies (full int input range);
and angle_0_to_2pi, angle_minuspi_to_pi.

This completes the itk::Math interface decoupling started with the
constants. <vnl/vnl_math.h> remains included so transitive users keep
compiling; its removal is deferred.
…lter

vnl_huge_val is slated for deprecation in upstream vxl. The int
overload returned INT_MAX as the initial min-energy sentinel; the
type-correct Float maximum serves the same purpose.
itk::Math::abs is a backward-compatibility forwarder to Absolute,
removed under ITK_FUTURE_LEGACY_REMOVE. Calling Absolute directly is
future-proof and unambiguous; behavior is identical.
…MOVE

itk::Math no longer uses anything from vnl_math.h. The include remains
for downstream code reaching vnl_math:: transitively through itkMath.h,
and disappears under ITK_FUTURE_LEGACY_REMOVE builds.
Handle the namespace-style vnl_math:: spellings (constants, functions,
predicates, std re-exports, abs) alongside the legacy vnl_math_*
globals, targeting C++17-compatible itk::Math/std equivalents. Report
vnl_huge_val occurrences for manual, type-aware migration.
Improvements over the vnl_math variants these functions replace:
- squared_magnitude(int/long/long long): multiply in the unsigned type;
  vnl's x * x is signed-overflow UB for |x| > sqrt(max).
- remainder_floored (signed integral): single-modulo conditional add;
  vnl's ((x % y) + y) % y overflows the intermediate for |y| > max/2.
- angle_0_to_2pi: drop an unreachable tail return inherited from vnl.
@hjmjohnson hjmjohnson force-pushed the itk-math-vnl-decouple branch from 9123d26 to f2c2a91 Compare June 10, 2026 22:28
Parity coverage mirrors the breadth of vxl's vnl_math test suite:
halfway-case rounding, floor/ceil, sign functions, powers,
squared_magnitude across all overload types, the remainder sign
matrix, angle normalization including the fmod boundary guard, and
the constants (sqrteps's intentional 1 ULP divergence asserted
explicitly).
@hjmjohnson hjmjohnson force-pushed the itk-math-vnl-decouple branch from f2c2a91 to faea421 Compare June 10, 2026 22:50
The test's purpose is calling vnl_math:: for comparison; once the VNL
deprecation campaign lands, those calls would trip warnings-as-fatal
CI gates. The opt-out macros are no-ops under the current VNL pin.
The VNL deprecation-campaign pin fixes vnl_math::sqrteps to the exact
2^-26; the prior pin rounds 1 ULP above. Accept either so the test
passes on both sides of the VNL update.
@hjmjohnson hjmjohnson requested a review from N-Dekker June 11, 2026 12:16
@hjmjohnson hjmjohnson dismissed N-Dekker’s stale review June 11, 2026 16:44

Already fixed! Thanks for you commment.

@hjmjohnson hjmjohnson merged commit 7d8e485 into InsightSoftwareConsortium:main Jun 11, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Core Issues affecting the Core module area:Documentation Issues affecting the Documentation module area:Filtering Issues affecting the Filtering module area:IO Issues affecting the IO module area:Python wrapping Python bindings for a class area:Registration Issues affecting the Registration module area:Segmentation Issues affecting the Segmentation module type:Enhancement Improvement of existing methods or implementation type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants