Skip to content

perf: avoid np.isclose in preprocess_strains_with_limits#361

Open
magnusfjeldolsen wants to merge 2 commits into
fib-international:devfrom
magnusfjeldolsen:perf/preprocess-strain-tol
Open

perf: avoid np.isclose in preprocess_strains_with_limits#361
magnusfjeldolsen wants to merge 2 commits into
fib-international:devfrom
magnusfjeldolsen:perf/preprocess-strain-tol

Conversation

@magnusfjeldolsen

Copy link
Copy Markdown
Contributor

Closes #360

ConstitutiveLaw.preprocess_strains_with_limits snaps strains that fall within a
small tolerance of an ultimate-strain limit onto that exact limit. It runs at the
top of every constitutive law's get_stress/get_tangent, so it is called for
every strain evaluation in the section integrators — for the fiber integrator,
once per fiber on each iteration of the equilibrium solver.

It previously called np.isclose twice and allocated two np.zeros_like(eps)
arrays on every call, even though it only compares each strain against the
constant limit. This PR precomputes the absolute tolerance as a scalar and uses a
plain abs(...) <= tol comparison.

Behaviour is unchanged

preprocess_strains_with_limits reads the limits once per call via
get_ultimate_strain(), so within a call the closeness threshold is a single
scalar. np.isclose(a, b) tests |a - b| <= atol + rtol * |b|; with b the
limit, atol=1e-6, and the default rtol=1e-5, the threshold is exactly
1e-6 + 1e-5 * |limit|. Computing that scalar once per call reproduces the same
boolean masks bit-for-bit — only the per-element np.isclose machinery and the
zeros_like allocations are removed.

Performance

Fiber integrator: ~1.8× faster calculations (median over 40 randomly generated
rectangular and circular RC sections; calculation time, with the build path
unchanged as a control). Marin gains little (~1.0–1.1×), since it routes far
fewer strains through this method.

Tests

Added tests parametrized over all nine constitutive laws, asserting results are
bit-identical to the documented np.isclose(atol=1e-6) reference and that
strains snap correctly. Full suite passes; ruff format and ruff check
(0.12.10) clean.

preprocess_strains_with_limits runs at the top of every constitutive law's
get_stress/get_tangent, so it is called for every strain evaluation in the
section integrators. It called np.isclose twice and allocated two zeros_like
temporaries on every call, even though it only compares each strain against
the constant ultimate-strain limit.

Replace this with precomputed scalar tolerances and plain abs(...) <= tol
comparisons that exactly reproduce np.isclose(atol=1e-6) with the default
rtol=1e-5, so results are bit-identical.

The fiber integrator benefits most, since every fiber goes through get_stress:
~1.8x faster calculations across randomly generated RC sections (median over
40 sections). Marin gains little, as it does far fewer strain evaluations.
Assert that the precomputed-threshold implementation reproduces the documented
np.isclose(atol=1e-6) tolerance bit-for-bit, and that strains within the band
snap to the exact limit while strains outside it are returned unchanged.
Parametrized over all nine constitutive laws.
@mortenengen mortenengen requested a review from talledodiego June 9, 2026 13:54
@mortenengen mortenengen added the enhancement New feature or request label Jun 9, 2026
@mortenengen mortenengen moved this to Under review 👀 in PR tracker Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Under review 👀

Development

Successfully merging this pull request may close these issues.

2 participants