Skip to content

kamal_sourour_rate: nth-order plus autocatalytic cure kinetics - #136

Merged
petlenz merged 42 commits into
mainfrom
feature/kamal-sourour-rate
Oct 27, 2026
Merged

petlenz merged 42 commits into
mainfrom
feature/kamal-sourour-rate

Conversation

@petlenz

@petlenz petlenz commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Closes #105. Part of the uvwmaterial port (#94); stacked on #135.

Of uvwmaterial's three kinetics (polymer_curing_functions), autocatalytic_reaction already exists as a rate law (curing_rate, k z^m (1 − z)^n) and as a backward-Euler material; the nth-order law is curing_rate with m = 0 (its derivative is exact there), so no new material for it. What was missing is the Kamal–Sourour combination.

materials/kamal_sourour_rate.h (kamal_sourour_rate): dz/dt = (k₁ + k₂ zᵐ)(1 − z)ⁿ with k_i = A_i exp(−E_i/(R T)), temperature from a scalar source in °C like curing_rate, the state from an integrator; outputs rate and rate_derivative. z is clamped to [0, 1] and the rate vanishes at full cure; the nth-order term starts the reaction from z = 0 without a seed.

Tests (test_kamal_sourour_rate, 8): rate against the formula and the derivative against central differences; A₂ = 0 reproduces curing_rate with m = 0 exactly for z > 0 (curing_rate returns zero at z = 0, its lower clamp, so the nth-order law there cannot start without a seed — worth its own small fix); the rate vanishes at z = 1 and is positive at z = 0; integration with rk_integrator (rk4) at 120 °C is monotone and reaches z > 0.99; creation from JSON.

No report: the report runner has no time-only path yet (#126).

Review follow-up: z^m is std::pow(z, m) as is (z⁰ = 1, so the nth-order case keeps k₂ at z = 0 from the first RK stage); only the slope is guarded, and only where it diverges (m < 1 at z = 0) — m = 1 gives k₂ − n k₁ at z = 0. A₁, E₁, A₂, E₂, m, n are rejected when negative. The clamp of z says that it absorbs an integrator stage's overshoot. New tests: the m = 0 and m = 1 identities at z = 0, the rejections.

924/924 tests pass (GCC 14, reports on).

JSON input

New material types: kamal_sourour_rate. A json_model document that loads (the FromJson test of the PR):

{"materials": [
  {"type": "scalar_stepper", "name": "temperature", "increment": 0.0},
  {"type": "scalar_stepper", "name": "z", "increment": 0.0},
  {"type": "kamal_sourour_rate", "name": "rate", "integrator_source": "z", "temperature_name": "temperature",
   "A1": 2e5, "E1": 60000.0, "A2": 1e6, "E2": 50000.0, "m": 0.7, "n": 1.3}
]}

dz/dt = (k₁ + k₂ zᵐ)(1 − z)ⁿ with kᵢ = Aᵢ exp(−Eᵢ / (R T)); integrator_source the state producer (the Runge–Kutta integrator of the repo in practice), temperature_name the temperature history (state); outputs rate, rate_derivative.

@petlenz

petlenz commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

Review findings (design, tmech expressions instead of component loops, modern C++, comments, tests, correctness). Severity: must = wrong result, hang, build break or unreachable feature; should = design, duplication, accuracy; nit = style.

kamal_sourour_rate — needs changes

  • [must] kamal_sourour_rate.h:171 zm = z > 0 ? pow(z,m) : 0 → 0 at z = 0 for m = 0 (z⁰ = 1); the advertised nth-order case loses k₂ on the first RK stage from z = 0. std::pow(0,m) is already 0 for m > 0; guard only the derivative.
  • [should] :172 dzm = 0 at z = 0 for m = 1 (should be 1); guard only for m < 1.
  • [should] no validation of m, n, A₁, A₂, E ≥ 0.
  • [nit] clamp hides integrator overshoot (say so); 6-line header; test the m = 0 identity at z = 0.

@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from 515796b to e4f80c1 Compare October 17, 2026 14:21
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch 2 times, most recently from b3e882d to 41afa98 Compare October 17, 2026 14:31
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from e4f80c1 to 316263a Compare October 17, 2026 14:31
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch from 41afa98 to c667526 Compare October 17, 2026 15:48
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from 316263a to 9928a69 Compare October 17, 2026 15:57
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch from c667526 to 829454b Compare October 17, 2026 16:57
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from 9928a69 to 971cf5a Compare October 17, 2026 17:04
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch from 829454b to 0c29804 Compare October 17, 2026 21:26
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from 971cf5a to 6966212 Compare October 17, 2026 21:36
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch from 0c29804 to ba5606b Compare October 17, 2026 22:46
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from 6966212 to 5463017 Compare October 17, 2026 22:46
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch from ba5606b to ca687fc Compare October 18, 2026 12:23
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from 5463017 to 8558f90 Compare October 18, 2026 12:32
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch from ca687fc to c32b2f9 Compare October 18, 2026 13:38
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch 2 times, most recently from e2a4434 to 39adb32 Compare October 18, 2026 18:55
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch from c32b2f9 to b075d06 Compare October 18, 2026 20:03
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from 39adb32 to 2afbfb9 Compare October 18, 2026 20:03
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch from b075d06 to 031b4e7 Compare October 18, 2026 20:22
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from 2afbfb9 to e7d2ffe Compare October 18, 2026 20:22
load_driver runs one material point under mixed strain/stress control
(uniaxial stress solves the five lateral strains with the material tangent)
and records the Newton residuals; material_report runs the load cases of a
case.json and writes CSV; reports/make_report.py turns them into figures and
the results section of docs/materials/<material>/README.md.
The correction solves (P:C:P + Isym - P + Iskw) : d_eps = -P:(sigma - sigma*)
with tmech::invf instead of a hand-written 6x6 elimination; Iskw makes the
operator regular on the full 9x9 space. Strain, stress and tangent are
tensors, converted to and from the evaluator buffers with tensor_conversion.
…n tensor, float-safe rotation_to

The elliptic integrals come from Boost.Math where they are first used (the
Eshelby tensors). rotation() builds [axis]_x as -(epsilon . axis); rotation_to()
uses 8 eps of the scalar type instead of 1e-14, which did not compile for float
and would have been c > 1 there. [[nodiscard]] on the pure helpers.
…per shape

eshelby_sphere, eshelby_cylinder, eshelby_spheroid and eshelby_ellipsoid consume
nu_source / nu_property and publish the output "tensor"; the engine orders and
re-evaluates them with nu. Two scalar producers feed them: poisson_ratio_from_moduli
(K_source, G_source -> value) and isotropic_moduli (tangent_source -> K, G,
poisson_ratio through isotropize, rejecting an anisotropic tangent). Composites
hold a node through eshelby_tensor_interface (tensor() at the wired nu, tensor(nu)
for the self-consistent iterate). core/eshelby.h keeps the closed forms: the
integrals come from Boost.Math (ellint_rd), the tensors are assembled on the
eigenprojectors, equal axes are detected at sqrt(eps).
core/isotropize.h: K = Pvol::C / Dim, G = Pdev::C / (2 (Dim(Dim+1)/2 - 1)),
and the isotropic part built from them. core/lebedev.h with generated
tables (tools/extract_lebedev.py from uvwmaterial's rule, precisions 3 to
23, up to 194 points): spherical means and integrals of tensor-valued
functions of the direction. eshelby::depolarisation for ellipsoids via
Carlson's R_J, the elliptic cylinder in closed form, and the cylinder along
a direction.
Phases are materials providing a tangent, with volume fractions; outputs
the effective tangent, the stress and the strain concentration tensors
(sum c_i A_i = Isym). Report with the closed forms of the three kernels
and their ordering. Report generator: derived values resolve in
dependency order, independent of the key order.
…ent producer over phases

The composite consumes the phases' produced tangents, fractions and Eshelby
tensors and produces the effective tangent and the concentrations; the stress
is a linear_stress on the tangent. It reaches into no other node: no
material_ref, no eshelby(nu), no isotropisation of the matrix.

Self-consistent is a graph cycle closed by one local edge: isotropic_moduli
on the composite tangent produces nu of the effective medium, the Eshelby
nodes read it with nu_edge = local (from nu_initial on the first pass), and
one update() is one relaxed Picard step with "residual" as its error. The
matrix is a phase with its own Eshelby node, so its shape is no longer a
hard-coded sphere. material_point_evaluator closes the fixed point per host
call (residual_property, residual_tolerance, max_passes; the report point
names them). isotropic_moduli with a non-positive tolerance projects without
the isotropy check, which the effective medium needs.
…ution

Spheroidal fibres in an isotropic matrix with the second-order orientation
tensor: Mori-Tanaka with the orientation-averaged dilute concentration, or
the averaged unidirectional stiffness. Eshelby tensors per direction are
cached for the matrix Poisson ratio. Report with aligned, injection-moulded
and random states against the unidirectional composite and isotropy.
core/orientation.h: fit_acg solves a_i = R_D(b_j, b_k, b_i) / 3 with
det b = 1 by Newton in the principal frame of the orientation tensor (any
A, not only diagonal), the aligned case as a single direction, planar
cases by an eigenvalue floor; nodes maps the Lebedev rule through B^-1/2;
average integrates a function of the direction.
… in the fibre frame

Both phases come in as produced properties (tangent, fraction, the fibre's
Eshelby tensor along x1); the composite consumes them, forms the dilute
concentration once in the fibre frame, rotates onto the quadrature directions
with otimesu(R, R) fixed per direction, and produces the tangent and the two
concentrations. The stress is a linear_stress on the tangent. No material_ref.
…history

sigma_{n+1} = sigma_n + C : (eps_{n+1} - eps_n) with the tangent read from
another material, for path-dependent tangents. Report: equals the total
form on every path, a closed path returns to zero, exact for any step.
Drivers: the convergence test also scales with |C| |d eps| (|A| |dF|), so
a hypoelastic stress crossing zero under stress control converges.
eps_th = alpha (T - T_ref) I from a scalar temperature source; linear
elasticity subtracts an optional eigenstrain: sigma = C : (eps - eps_eig).
dz/dt = (k1 + k2 z^m) (1 - z)^n with Arrhenius rate constants, as a rate
law for rk_integrator next to curing_rate (the autocatalytic term alone;
nth order is curing_rate with m = 0).
@petlenz
petlenz force-pushed the feature/thermal-eigenstrain branch from 031b4e7 to 25909f3 Compare October 19, 2026 04:06
@petlenz
petlenz force-pushed the feature/kamal-sourour-rate branch from e7d2ffe to 9037f62 Compare October 19, 2026 04:07
Main has the reviewed split of this branch (#199-#203), so main's version is
taken everywhere; the branch no longer differs from main.
…ls-and-constants

# Conflicts:
#	tests/CMakeLists.txt
@petlenz
petlenz changed the base branch from feature/thermal-eigenstrain to main October 24, 2026 21:16
…eshelby-tensors

# Conflicts:
#	tests/CMakeLists.txt
…v-depolarisation

# Conflicts:
#	tests/CMakeLists.txt
…/rule-of-mixture

# Conflicts:
#	tests/CMakeLists.txt
# Conflicts:
#	include/numsim-materials/report/report_json.h
#	tests/CMakeLists.txt
residual_source, residual_tolerance and max_passes are driver parameters
(material_point's defaults) and keys of the report's "point"; the mean-field
tangent report uses them for the self-consistent fixed point.
…averaging

# Conflicts:
#	tests/CMakeLists.txt
…-composite

# Conflicts:
#	tests/CMakeLists.txt
…-elasticity

# Conflicts:
#	include/numsim-materials/umat/deformation_gradient_driver.h
#	include/numsim-materials/umat/material_point_driver.h
#	tests/CMakeLists.txt
…genstrain

# Conflicts:
#	tests/CMakeLists.txt
@petlenz
petlenz marked this pull request as ready for review October 27, 2026 17:50
@petlenz
petlenz merged commit 9b7379e into main Oct 27, 2026
2 checks passed
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.

Curing kinetics: nth-order and nth-order autocatalytic reactions

1 participant