Deep, code-cited reference. See the readable summary for the public version. This page is the full provenance; a Verification addendum (run wf_7fbcd118-848, 2026-06-08) at the bottom lists every point where the code differs from this text.

Data regenerated 2026-06-15 (relax-cell fix). The elastic numbers shown on the site were rebuilt from a standalone relax-cell campaign (59 potentials; m3gnet_MP-2021.2.8-EFS was not re-run and is no longer in the elastic track). The earlier deployed data had been computed without a cell-relaxed reference (only relax_cell=False was active), so it claimed the variable-cell reference described here but did not perform it. The regenerated data uses the variable-cell reference relaxation (relax_cell=True, §2) + fixed-cell deformations, so the data now matches this method. DFT references (dft_k_vrh/dft_g_vrh, DFT tensors) are method-independent and unchanged.

Elastic-Constant Calculation — Full Method, Provenance & Adversarial Verification

Benchmark: mlip-elemental-benchmark, Track C (mlip_elastic) Question: full elastic stiffness tensor Cᵢⱼ + derived moduli (K, G, E, ν, Aᵁ) + Born mechanical-stability for each of 834 MP elemental crystals × 59 MLIP potentials, compared against Materials-Project DFT elastic references where available. Method family: Materials-Project stress–strain (finite-deformation) via pymatgen.analysis.elasticity, driven by an ASE calculator through pyiron_workflow_atomistics.physics.elastic. Documented & verified: 2026-06-08 · Author: Han

Note on the site methodology. The live site methodology.md Track C text is essentially correct. The cell relaxation described below is a recent code addition (the reference relaxation now relaxes the cell via a filter), so a site that still describes a fixed-cell reference is stale, not wrong — it documented the earlier behaviour. The one genuine standing correction is the iteration cap (§6): the documented max_iterations=300 is silently overridden to 10 000. Everything else in §1–§5 is the code-true method.


0. Source-of-truth files (every claim here is cited to these)

Layer File
Entry / DB-row builder benchmark/elastic_compute.py
Algorithm (macro) pyiron_workflow_atomistics/physics/elastic.py
ASE relax/stress engine …/pyiron_workflow_atomistics/engine/ase.py
Calc-input dataclasses …/pyiron_workflow_atomistics/engine/inputs.py
Per-deformation driver …/pyiron_workflow_atomistics/physics/bulk.py (evaluate_structures)
DB worker benchmark/elastic_worker.py
Schema benchmark/schema_elastic.sql
Error metric benchmark/eval/elastic_metrics.py
Rollup benchmark/eval/build_rollups.py (elastic_rollup)

1. End-to-end pipeline

structure.atoms_pickle (plain ASE Atoms, the potential-independent MP cell)
        │
        │  elastic_worker.process_one  →  elastic_compute.compute_elastic(atoms, calc, fmax=1e-2, max_iterations=300)
        ▼
calculate_elastic_constants(structure, engine=ASEEngine(calc), relax_initial=True, fmax=1e-2, max_iterations=300)
        │
   ┌────┴─────────────────────────────────────────────────────────────────────┐
   │ (1) REFERENCE RELAX  — CalcInputMinimize(relax_cell=True)  → ExpCellFilter │  full cell+ion
   │     ref_structure = relaxed cell;  eq_stress = its residual stress (GPa)   │  relaxation
   ├───────────────────────────────────────────────────────────────────────────┤
   │ (2) DEFORM  — pymatgen DeformedStructureSet → 6 modes × 4 magnitudes = 24  │  Green-Lagrange
   │     norm ±0.5 %, ±1 % ;  shear ±3 %, ±6 %                                  │  strains
   ├───────────────────────────────────────────────────────────────────────────┤
   │ (3) PER-DEFORMATION  — CalcInputMinimize(relax_cell=False)  (ion relax at  │  fixed-cell
   │     fixed cell), then read final stress → 24 stress tensors (GPa)          │  ion relax + stress
   ├───────────────────────────────────────────────────────────────────────────┤
   │ (4) FIT  — ElasticTensor.from_independent_strains(strains, stresses,        │  least-squares
   │     eq_stress=eq_stress, vasp=False) → voigt_symmetrized 6×6 C (GPa)        │  6×6 stiffness
   ├───────────────────────────────────────────────────────────────────────────┤
   │ (5) SUMMARY  — K/G (Voigt/Reuss/VRH), E, ν, Aᵁ, IEEE tensor, compliance,    │  pymatgen
   │     stiffness eigenvalues, mechanically_stable (general Born)               │  elasticity
   └───────────────────────────────────────────────────────────────────────────┘
        │
        ▼  UPSERT into mlip_elastic  (UNIQUE(structure_id, potential)), status done/error

1.5 The two relaxation variants (used at different steps)

Every relaxation in the workflow is ASE BFGS with fmax = 1e-2 eV/Å; they differ only in what is allowed to move. The engine exposes both through CalcInputMinimize.relax_cell (engine/inputs.py), and the elastic workflow deliberately uses both:

Variant relax_cell What moves ASE mechanism Used for
Fixed-cell False ionic positions only; cell frozen BFGS(atoms) (bare atoms, no filter) — engine/ase.py:208-225 the 24 deformed cells (§4): the strained cell must stay strained while ions relax, so the stress reflects that strain
Variable-cell (with filter) True full lattice and ions BFGS(ExpCellFilter(atoms))engine/ase.py:178-185 the reference cell (§2): find the MLIP's own equilibrium cell before deforming

(The pwa elastic engine uses ExpCellFilter for the variable-cell variant. This is the direct analogue of the two stability-ordering tracks — fixed-cell relax vs variable-cell relax_cell, where that worker uses FrechetCellFilter.)

The variable-cell reference relaxation is the recently-added behaviour; earlier the reference was relaxed fixed-cell, which is what older methodology text describes.


2. Step 1 — reference relaxation (relax_initial=True) — variable-cell variant

What this step does: before any strain is applied, it moves the structure to the MLIP's own equilibrium cell so the elastic constants are evaluated about a stress-free reference (any residual stress is captured and subtracted in the fit). elastic.py:272-282.

If relax_initial=False, the input is deformed as-is and eq_stress=None. The campaign always uses relax_initial=True (elastic_compute.py:16).


3. Step 2 — the deformation set (24 cells)

generate_mp_deformations, elastic.py:47-71. Uses pymatgen.analysis.elasticity.strain.DeformedStructureSet on the relaxed reference cell:

This is the Materials-Project standard deformation grid (the MP elasticity methodology uses the same ±0.5/±1 % normal, ±3/±6 % shear set).


4. Step 3 — per-deformation stress (fixed-cell variant)

What this step does: for each strained cell, it lets the ions settle within that fixed strain and then reads the resulting stress. The 24 (strain, stress) pairs are the raw data the stiffness tensor is fitted from. evaluate_structures (physics/bulk.py:63-75) runs each of the 24 deformed cells through the engine configured with CalcInputMinimize(relax_cell=False, …) (elastic.py:266-269, 290-294):


5. Steps 4–5 — fit and derived constants

5.1 Tensor fit — fit_elastic_tensor, elastic.py:88-108

ElasticTensor.from_independent_strains(strains, stresses, eq_stress=eq_stress, vasp=False)
→ ElasticTensor(et.voigt_symmetrized)

5.2 Derived constants — elastic_constants_summary, elastic.py:111-185 (all GPa unless noted)

Field Source Notes
K_Voigt,K_Reuss,K_VRH pymatgen et.k_voigt/k_reuss/k_vrh bulk modulus, Voigt/Reuss/Hill
G_Voigt,G_Reuss,G_VRH pymatgen et.g_voigt/g_reuss/g_vrh shear modulus, Voigt/Reuss/Hill
youngs_modulus 9·K_VRH·G_VRH/(3·K_VRH+G_VRH) (elastic.py:167) from VRH K,G
poisson_ratio et.homogeneous_poisson dimensionless
universal_anisotropy et.universal_anisotropy dimensionless (Aᵁ)
stiffness_eigenvalues eigvalsh(C_voigt) 6 eigenvalues (GPa)
elastic_tensor_ieee et.convert_to_ieee(pmg) falls back to unrotated tensor if IEEE rotation undefined (elastic.py:150-155)
compliance_tensor_voigt et.compliance_tensor.voigt units 1/GPa
mechanically_stable all(eigvalsh(C_voigt) > 1e-8 GPa) (elastic.py:159-161) general Born (positive-definite stiffness); see §6.3

5.3 The DB row — elastic_compute.py:25-45, elastic_worker.py


6. The one genuine standing correction + provenance notes

The site methodology and the code agree on the science. Every constant, formula, and step in §1–§5 was independently re-checked by 8 adversarial agents that read the source and tried to refute each claim (workflow verify-elastic-methodology, run wf_daca20a2-57b): all confirmed at high confidence. What follows is the one item that should actually change in the code/docs, plus provenance notes that explain why older text reads differently.

6.1 — ⚠️ Iteration cap: documented max_iterations=300 is silently overridden to 10 000

This is the real bug. elastic_compute.py:13 passes max_iterations=300, but the ASE dispatch picks self.max_steps if self.max_steps else mi.max_iterations (engine/ase.py:459), and ASEEngine.max_steps defaults to 10 000 (engine/ase.py:416) — which is truthy, so the 300 is never used. Effective optimiser cap = 10 000 steps for the reference and every deformation. Usually benign (relaxations hit fmax long before either cap), but the intended 300 is dead. Action: either set ASEEngine(max_steps=300) to honour the intent, or update docs to state the real cap is 10 000.

6.2 — Cell relaxation of the reference is a recent addition (older text is stale, not wrong)

The reference relaxation now uses the variable-cell variant (relax_cell=TrueExpCellFilter, §2). This relaxes lattice + ions; earlier the reference was relaxed fixed-cell. Methodology text that still says the reference is "ion relax at fixed cell" simply predates the change — it was correct for the earlier code. The two variants (§1.5) are now both in play: variable-cell for the reference, fixed-cell for the deformations. Update any stale text to reflect the variable-cell reference; this is a doc refresh, not a correction of an error.

6.3 — Minor notes (no action needed)

Verified correct (no change)


7. Metrics & rollup (how Cᵢⱼ becomes a leaderboard number)


8. Reproduce one structure

PY=python   # has pymatgen + pyiron_workflow_atomistics
# (model-specific potentials need their envs_elemental/<pot> env instead)
from benchmark.db import connect, from_bytea
from benchmark.elastic_compute import compute_elastic
from benchmark.worker import _resolve_factory
calc,_ = _resolve_factory("mace_MP-0")()
with connect() as c, c.cursor() as cur:
    cur.execute("SELECT atoms_pickle FROM structure WHERE element='Cu' AND is_ground_state LIMIT 1")
    atoms = from_bytea(cur.fetchone()[0])
res = compute_elastic(atoms, calc)        # fmax=1e-2, full-cell ref relax, 24 deformations
print(res["k_vrh"], res["g_vrh"], res["mechanically_stable"])

Inputs that fully determine a row: the MP cell, the potential's calculator, fmax=1e-2, the fixed 24-cell deformation grid, and the pymatgen fit. Everything else is bookkeeping.


Verification addendum (run wf_7fbcd118-848, 2026-06-08)

All 30 claims confirmed against the code. Standing items already noted in §6 of this doc and carried to the summary: effective optimiser cap is 10000 steps, not 300 (engine/ase.py:416,459 shadows max_iterations); campaign fmax=1e-2 (not the macro's 1e-3); Born stability is the general positive-definite criterion only (eigenvalues > 1e-8 GPa), not "cubic + general" (the docstring overstates). Note: on an errored re-run the UPSERT can overwrite a prior done row's physics with NULLs (elastic_worker.py:41-50).