Skip to content

fix(core): Do not raise floating point exceptions during create/init - #405

Open
hgopalan wants to merge 4 commits into
FloatingArrayDesign:devfrom
hgopalan:fix/cfl-sentinel-overflow
Open

hgopalan wants to merge 4 commits into
FloatingArrayDesign:devfrom
hgopalan:fix/cfl-sentinel-overflow

Conversation

@hgopalan

@hgopalan hgopalan commented Oct 2, 2026

Copy link
Copy Markdown

Fixes #404.

MoorDyn_Init() (and in some configurations MoorDyn_Create()) raised FE_OVERFLOW, FE_DIVBYZERO or FE_INVALID on intermediate values that are discarded afterwards. The results are correct, but a host program that traps floating-point exceptions (feenableexcept() on Linux, FPCR trap bits on macOS arm64) gets killed. While checking the maintainer's question in #404 (whether anything else trips once the CFL overflow is patched), I ran the whole test suite with the traps enabled and found three more sites. This PR fixes all of them.

Changes

Site Exception Fix
CFL::cfl2dt(cfl, v): cfl * max() / v for points, rods and bodies (the case reported in #404) overflow return the max() sentinel unchanged
CFL::cfl2dt(cfl, v) with v == 0 in StationaryScheme::Step() div-by-zero return max()
NatFreqCFL::cfl2dt(cfl) with cfl = max(), which MoorDyn::Init() sets when dtM is prescribed, for lines whose segment natural period is > 1 s overflow return the max() sentinel unchanged
Rod::setup(): i / (real)N with N = 0 for zero-length rods invalid (0/0) use f = 0
Catenary() Newton-Raphson iterations (weightless or buoyant lines, log/sqrt of negatives, x/0) called from Line::initialize() invalid, div-by-zero, overflow hold the FP environment around the call (feholdexcept/fesetenv) and discard the flags; the solver already reports the failure and the line falls back to the linear profile

In every case the old value was inf/nan and was already dropped by std::min or by the caller, so results do not change. The catenary change leaves the solver itself untouched. If you'd rather guard each operation inside Catenary(), I can do that instead, but it is a much more invasive change to that routine.

tests/fpe.cpp adds a portable test (no traps needed) that clears the flags, then runs create, init and 10 steps on 5 inputs covering the sites above, and requires fetestexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW) == 0.

Testing

macOS 26 arm64, AppleClang, RelWithDebInfo, PYTHON_WRAPPER=OFF:

  • ctest: 32/32 pass.

  • ctest with the traps enabled in every test process (a preloaded library sets the FPCR trap bits):

    trap dev this PR
    overflow 19 / 31 tests killed 0
    div-by-zero 9 / 31 tests killed (with the CFL overflow fixed) 0
    invalid 12 / 31 tests killed (with the CFL overflow fixed) 0
    all three — 0 (32/32 pass)
  • tests/fpe.cpp on dev: 5/5 cases fail; with this PR: 5/5 pass.

  • The *.out files written by the test suite (76 files) are byte-identical between dev and this branch.

  • The reproducer from MoorDyn_Init raises FE_OVERFLOW in CFL::cfl2dt (stationary IC solver); fatal when the host traps floating-point exceptions #404 (line between two fixed points in air, external kinematics) runs Init plus 60 s of simulated time (6000 steps of 0.01 s with a gusty cross-flow) with all three traps enabled. It raises no flags and gives the same fairlead tension as dev.

🤖 Generated with Claude Code

hgopalan and others added 4 commits October 2, 2026 07:13
…alues

Points, rods and bodies keep length = max() as a sentinel, and MoorDyn::Init()
sets cfl = max() when dtM is prescribed. Multiplying those sentinels raised
FE_OVERFLOW, and a null velocity in the stationary solver raised FE_DIVBYZERO.
The resulting inf/nan values were discarded by std::min(), so the time step is
unchanged, but the host program is killed if it traps those exceptions.

Fixes FloatingArrayDesign#404

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The interpolation factor i / N was 0 / 0 for zero-length rods (N = 0),
raising FE_INVALID and setting the node position to nan until it was
overwritten later on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…C solver

The Newton-Raphson iterations of Catenary() may visit points out of the domain
of the involved functions (e.g. weightless or buoyant lines), raising
FE_INVALID, FE_DIVBYZERO or FE_OVERFLOW. The solver detects those failures by
itself, and Line::initialize() falls back to the linear profile, so the raised
exceptions are now held while the solver runs and discarded afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant