Skip to content

Speed up preprocess_strains_with_limits by removing per-call np.isclose #360

Description

@magnusfjeldolsen

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 the integrators
call it every time they evaluate stress or tangential stiffness — for the fiber
integrator, once per fiber on each iteration of the equilibrium solver (typically
tens of thousands of strain values per moment curvature calculation).

On every call it does more work than needed: it runs np.isclose twice and
allocates two np.zeros_like(eps) arrays, even though it is only comparing each
strain against the limit.

idxs = np.isclose(eps, np.zeros_like(eps) + eps_max, atol=1e-6)
eps[idxs] = eps_max
idxs = np.isclose(eps, np.zeros_like(eps) + eps_min, atol=1e-6)
eps[idxs] = eps_min

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 shared by every value in eps. np.isclose(a, b) tests
|a - b| <= atol + rtol * |b|
(numpy docs);
here b is the limit, atol=1e-6 is passed and rtol keeps its default 1e-5,
so the threshold is just 1e-6 + 1e-5 * |limit|. Precomputing that scalar once
per call and comparing with a plain abs(...) <= tol gives bit-identical masks
for every law — it removes only the per-element np.isclose machinery and the
zeros_like allocations, not any behaviour.

Fiber benefits most — every fiber goes through this method. For Marin it usually
only benefits the rebars, except when Marin falls back to discretizing a
constitutive law whose stress–strain curve isn't a polynomial.

Example: 40 randomly generated sections, fiber integrator

Each section is randomly rectangular or circular (50/50), with parameters drawn
from:

parameter rectangular circular
outer size [mm] width 200–500, height 300–800 diameter 300–800
cover [mm] 30–50 30–50
bar diameter [mm] 16 / 20 / 25 / 32 16 / 20 / 25 / 32
number of bars 2–5 per layer (top + bottom) 6–12 around perimeter
concrete class C25–C45 C25–C45

Each section runs bending capacity + moment–curvature + N–M interaction domain,
with the current np.isclose implementation timed against the
precomputed-threshold version (identical results on every section):

metric per-section speedup
calculation time median 1.82×, range 1.56–2.15× (p10 1.69, p90 2.04)

So roughly a ~1.8× faster fiber calculation.

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