Skip to content

Nonlinear phases in the local mean field: a local tangent edge and passes to the fixed point - #147

Open
petlenz wants to merge 2 commits into
feature/mean-field-local-strainfrom
feature/mean-field-local-nonlinear
Open

petlenz wants to merge 2 commits into
feature/mean-field-local-strainfrom
feature/mean-field-local-nonlinear

Conversation

@petlenz

@petlenz petlenz commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Closes #116 (the nonlinear half; the linear localisation is #144). Part of the uvwmaterial port (#94); stacked on #144.

A phase whose tangent depends on its own strain closes a cycle through mean_field_strain: the concentrations need the algorithmic tangent, the tangent needs the local strain. This PR adds the one piece that closes it and the tests that exercise it with an isotropic-damage matrix.

The local edge. a phase whose tangent depends on its own strain closes a cycle. Its phase node reads the tangent over a local edge ("tangent_edge": "local", starting from initial_tangent_source on the first pass): EdgeKind::Local imposes no ordering, so one update() is one pass with the algorithmic tangent of the previous pass, and the fixed point is run by whoever calls update() — the evaluator per host call (residual_property of #131, the report point's "residual": "mt::residual"), a solver's Newton loop, or a while (residual > tol) update() in a test. Isotropic damage keeps Poisson's ratio, so the Eshelby node reads ν of the undamaged tangent; an anisotropic algorithmic tangent would use isotropic_moduli with a local nu_edge the same way.

Tests (test_mean_field_damage, 9, stiff elastic spheres in a damaging matrix): below the threshold the damage chain reproduces the linear composite to 1e-10; after damage the local strains partition the composite strain (c_m ε_m + c_i ε_i = ε to 1e-12), the state function saw exactly the matrix strain, the spheres' stress is their tangent at their strain, the residual is converged, the stress is the phase average and the matrix softened below the intact composite; the tangent is Σ c_r C_r : A_r; the residual decreases over the passes of a large damaging step and reaches 1e-10; the tangent matches central differences on a vanishing step through the evaluator (< 1e-2); stress control through the driver converges and damages; the Reuss kernel with a hardening J2 matrix equalises the phase stresses to first order in the step (a softening phase in series is a snap-back problem, noted in the docs); a local edge without an initial tangent and non-parallel source lists are rejected; JSON.

Converter: a rule-of-mixture card with a nonlinear phase becomes the local pair for every kernel (the damage chain reads strain_<phase>, the damage phase carries the local edge from its elastic tangent except for Voigt, the point gets the residual) — the Reuss/Hill cases that raised #117 before.

Docs: the nonlinear section and the fixed-point section of docs/mean_field.md.

Reports docs/materials/mean_field_damage (the local pair with a damaging matrix against the intact Mori–Tanaka: composite and phase stresses, damage, local strains under strain and stress control; partition, phase-average, residual and softer-on-first-loading checks — after a reversal the incremental localisation leaves a residual stress, noted) and docs/materials/mean_field_reuss_plastic (the Reuss kernel through the passes with a hardening J2 matrix: equal phase stresses to first order in the step). Their finite-difference tangent checks are off: the tangents are first order in Δε.

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

JSON input

{"materials": [
  {"type": "external_strain_source", "name": "strain"},
  {"type": "constant_scalar", "name": "Km", "value": 3333.33}, {"type": "constant_scalar", "name": "Gm", "value": 1153.85},
  {"type": "constant_scalar", "name": "Ki", "value": 166666.67}, {"type": "constant_scalar", "name": "Gi", "value": 76923.08},
  {"type": "isotropic_tangent", "name": "C_m", "K_source": "Km", "G_source": "Gm"},
  {"type": "isotropic_tangent", "name": "C_i", "K_source": "Ki", "G_source": "Gi"},
  {"type": "isotropic_moduli", "name": "moduli", "tangent_source": "C_m"},
  {"type": "eshelby_sphere", "name": "sphere", "nu_source": "moduli", "nu_property": "poisson_ratio"},
  {"type": "constant_scalar", "name": "c", "value": 0.3},
  {"type": "constant_scalar", "name": "cm", "value": 0.7},
  {"type": "phase", "name": "matrix", "tangent_source": "damage", "tangent_property": "damaged_tangent",
   "tangent_edge": "local", "initial_tangent_source": "C_m", "fraction_source": "cm"},
  {"type": "phase", "name": "spheres", "tangent_source": "C_i", "fraction_source": "c", "eshelby_source": "sphere"},
  {"type": "mean_field_strain", "name": "mt_strain", "strain_source": "strain", "matrix": "matrix", "phases": ["spheres"], "kernel": "mori_tanaka"},
  {"type": "linear_elasticity", "name": "elastic", "strain_producer_name": "mt_strain", "strain_property": "strain_matrix", "K": 3333.33, "G": 1153.85},
  {"type": "von_mises_state_function", "name": "state", "strain_source": "mt_strain", "strain_property": "strain_matrix"},
  {"type": "strain_threshold_yield", "name": "yield", "state_source": "state", "kappa_critical": 0.002},
  {"type": "exponential_damage_law", "name": "law", "yield_source": "yield", "kappa_0": 0.002, "beta": 30.0},
  {"type": "isotropic_damage", "name": "damage", "elastic_source": "elastic", "damage_source": "law", "state_source": "state", "yield_source": "yield"},
  {"type": "linear_stress", "name": "stiff", "tangent_source": "C_i", "strain_source": "mt_strain", "strain_property": "strain_spheres"},
  {"type": "mean_field_stress", "name": "mt", "concentration_source": "mt_strain", "matrix": "matrix", "phases": ["spheres"],
   "matrix_stress_source": "damage", "matrix_stress_property": "damaged_stress", "matrix_tangent_property": "damaged_tangent",
   "stress_sources": ["stiff"], "tangent_sources": ["C_i"]}
 ],
 "point": {"strain_source": "strain", "stress_source": "mt", "residual": "mt::residual", "tolerance": 1e-10, "max_passes": 200}}

Linear phases are #144: the same two nodes without the local edge.

@petlenz
petlenz force-pushed the feature/mean-field-local-strain branch from 4506dbc to f17f10d Compare October 18, 2026 18:55
@petlenz
petlenz force-pushed the feature/mean-field-local-nonlinear branch from 5f16f95 to 6540b58 Compare October 18, 2026 18:55
@petlenz
petlenz force-pushed the feature/mean-field-local-strain branch from f17f10d to a0a7d3b Compare October 18, 2026 20:03
@petlenz
petlenz force-pushed the feature/mean-field-local-nonlinear branch from 6540b58 to e9c11c8 Compare October 18, 2026 20:03
@petlenz
petlenz force-pushed the feature/mean-field-local-strain branch from a0a7d3b to 7f55103 Compare October 18, 2026 20:22
@petlenz
petlenz force-pushed the feature/mean-field-local-nonlinear branch from e9c11c8 to 58314fd Compare October 18, 2026 20:22
… phase, passes to the fixed point

A phase whose tangent depends on its own strain closes a cycle through
mean_field_strain: the concentrations need the algorithmic tangent, the
tangent needs the local strain. The phase node reads that tangent with
tangent_edge = local (no ordering, the value of the previous pass; the
initial_tangent_source on the first pass), so one update() is one pass and
mean_field_stress::residual says when the passes agree. The evaluator
closes the fixed point per host call through residual_property.

Tests with an isotropic-damage matrix and stiff spheres: below the threshold
the chain equals the linear composite; after damage the local strains
partition the composite strain, the state function saw the matrix strain, the
stress is the phase average, the tangent is the concentration-weighted sum and
matches central differences on a vanishing step; the residual decreases over
the passes of a large step; stress control through the driver converges; the
Reuss kernel ends with equal stresses in both phases. The report
mean_field_damage runs the standard cases with the point closing the fixed
point; the card converter maps a rule-of-mixture card with a nonlinear phase
to the pair for every kernel.
…se with a hardening J2 matrix

mean_field_damage records the intact Mori-Tanaka next to the local pair
(composite and phase stresses, damage, local strains under strain and stress
control; on first loading the damaged composite is softer, after a reversal
the incremental localisation leaves a residual stress, noted). The new case
mean_field_reuss_plastic runs the Reuss kernel through the passes with a J2
matrix: the phases carry the same stress to first order in the step.
@petlenz
petlenz force-pushed the feature/mean-field-local-strain branch from 7f55103 to 427f671 Compare October 19, 2026 04:07
@petlenz
petlenz force-pushed the feature/mean-field-local-nonlinear branch from 58314fd to d8789ec Compare October 19, 2026 04:07
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