Skip to content

Declared partials for the elastic producers and the damage chain (#219, part 1) - #230

Open
petlenz wants to merge 5 commits into
mainfrom
feature/issue-219-elastic-damage
Open

petlenz wants to merge 5 commits into
mainfrom
feature/issue-219-elastic-damage

Conversation

@petlenz

@petlenz petlenz commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Part of #219 (epic #221): the checklist items Elasticity and stress producers and Damage. Stacked on #227 (partials API, #217), which sits on #223. Merge after #223 and #227.

What / why

The elastic producers and the damage chain declare their partials, so material_point::derivative("…::stress" / "…::damaged_stress", "strain") accumulates the tangent from the graph instead of the hand-written chain rule in isotropic_damage.

material declared partials
linear_stress ∂σ/∂ε = C, ∂σ/∂C = 𝕀ˢ ⊗ ε
isotropic_tangent ∂C/∂K = I ⊗ I, ∂C/∂G = 2 ℙdev
isotropic_moduli ∂K/∂C = ℙvol/3, ∂G/∂C = ℙdev/10, ∂ν/∂C via K and G
incremental_linear_elasticity ∂σ/∂ε = tangent, ∂σ/∂tangent = 𝕀ˢ ⊗ Δε
von_mises_state_function, vector_strain_state_function ∂ε_eq/∂ε (= d_equivalent_strain)
strain_energy_state_function ∂ε_eq/∂ε, ∂ε_eq/∂C = ε ⊗ ε / (2E ε_eq)
strain_threshold_yield ∂κ/∂ε_eq = 1 while yielding, else 0
damage laws (power, saturation, Peerlings, exponential, linear; in detail/scalar_damage_law.h) ∂D/∂κ (0 below κ₀ and at the cap)
isotropic_damage ∂σd/∂σ = (1 − D) 𝕀ˢ, ∂σd/∂D = −σ

linear_elasticity and thermal_eigenstrain (also the curing shrinkage) already declare theirs in #227.

All existing outputs stay: tangent, damaged_tangent, d_damage, d_equivalent_strain, and the is_yielding read. Removing them is a follow-up once the hosts read derivatives.

Decisions

  • isotropic_damage now has two callbacks. One computes damaged_stress from (σ, D) and declares its partials. The other computes damaged_tangent from the old reads. Partials are declared per callback. With a single callback, a derivative of damaged_stress would have to pass through d_equivalent_strain, d_damage and the tangent. Those reads would then need Hessians, or an independent_of() that would be false for damaged_tangent.
  • The damage law declares the true slope ∂D/∂κ, also while unloading. The zero during unloading comes from the yield node (∂κ/∂ε_eq = 0 when not yielding). The law only reads κ, so a finite-difference check of the law alone gives the slope. The issue text puts the zero in the law. Here the product along the chain is the same, and every node passes its own check.
  • neo_hooke and hencky_elasticity are not migrated. Partial derivatives: per-callback declarations and forward accumulation (#217) #227 packs only symmetric tensors: tensor2 is a 6-component Mandel block, and packing projects onto the symmetric part. P and F are not symmetric, so declaring dP/dF would silently give the symmetrised derivative. Also, external_deformation_gradient_source sets inverse and determinant in bind(), without a callback, so they carry no derivative from F. This needs the 9-component layout from the Property engine: partial derivatives per callback, chain rule by forward accumulation #217 table in the engine, which is a follow-up to Property engine: partial derivatives per callback, chain rule by forward accumulation #217.
  • incremental_linear_elasticity::copy_tangent declares nothing, because it would need a rank-8 identity. A derivative through a strain-dependent tangent throws and names that path.
  • isotropic_moduli uses reads(m_C) instead of reads_all_inputs(). C is its only input; the change lets the callback declare partials.
  • test_material_conformance: the finite-difference model now covers every new material. Its state is scaled to strains of about 0.1, so central differences of the nonlinear state functions stay within 1e-6. The header scan also counts materials that include a detail/ base that declares partials (the damage laws).

Tests

Gate from the issue: the accumulated derivative equals the hand-written tangent to 1e-12 (relative) at every step, for loading, unloading and reloading.

  • New test_accumulated_tangents: runs the load cases of the report decks linear_elasticity, incremental_linear_elasticity, isotropic_damage, mean_field_damage (phases at their local strains), short_fibre_damage (all 50 fibre chains, linear and nonlinear localisation), mean_field_composite_global, …_rule_of_mixture and short_fibre_composite. The cases go through zero to compression and back, under strain and stress control, with fewer steps than the reports. short_fibre_damage runs only its uniaxial-strain case: its nonlinear localisation needs small steps.
  • Gates added next to the existing test models: test_damage (the stepper model, and the chain over all three state functions), test_damage_laws (each law against is_yielding · d_damage · d_equivalent_strain, up to the cap), test_mean_field_damage, test_orientation_phases (damaging fibres, both localisations), test_curing_eigenstrain (∂σ/∂ε and ∂σ/∂ξ = −C:βI), test_uncovered_materials (state functions) and test_incremental_elasticity.
  • TDD: before the partials, every derivative request threw and named the path, e.g. stepper::strain -> state_func::equivalent_strain -> state_func::d_equivalent_strain -> damaged::damaged_stress.

No accumulated tangent differed from the hand-written one.

Gate

GCC 14 (-DNUMSIM_PREFER_SIBLINGS=OFF, reports on): BUILD=0, 1540/1540 ctest tests passed (incl. ASan variants and report decks), 0 warnings.

)

The edges keep their kind after the sort; material_context::graph() returns the nodes with their flags and the typed edges, to_dot()/to_json() export them. A cycle is reported with its properties and edge kinds. update_property() caches the upstream set per target.
…on (#217)

Callbacks declare partials() / independent_of() next to reads(); the engine
plans a requested derivative over the target's upstream and accumulates the
Mandel-packed partials. Hosts request derivatives via material_context and
material_point. check_partials() verifies declared partials by central
differences. thermal_eigenstrain and linear_elasticity declare their partials,
giving the first DDSDDT.
linear_stress, isotropic_tangent, isotropic_moduli, incremental_linear_elasticity,
the state functions, strain_threshold_yield, the scalar damage laws and
isotropic_damage declare their partials; isotropic_damage splits the damaged
stress from the damaged tangent so the stress carries a derivative. The
accumulated derivative equals the hand-written tangents to 1e-12 along the
report load paths and the existing test models, loading and unloading. All
existing tangent outputs stay.
With the partials requested, a rerun at a perturbed read refilled the blocks, so a later partial of the same callback was compared at the perturbed state. Found while building #218.
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