Material calibration#
Calibrate six crystal-plasticity parameters (elastic E, nu, G, slip strength, Voce hardening slope and saturation) to a macroscopic stress-strain curve plus full-field elastic strains. Fitting uses a neml2 v3 + pyzag analytic-adjoint Taylor model driven by LBFGS, through MaterialCalibration and TaylorModel. Run this to fit material parameters before CPFE.
Needs: NEML2 v3 only (no MOOSE); CUDA optional. See installation.
Tier B: needs NEML2 v3 + pyzag
This notebook needs NEML2 v3 with pyzag, which is not available in CI or Google Colab, so it is not executed when these docs are built. It ships as an annotated code listing with no stored output — nothing on this page is a hand-written or fabricated result. Run it in a configured environment (see the installation guide) to produce the figures, and run it from the repository root so the mwe_data/ paths resolve.
Inputs#
A folder of per-stress-level CSVs with the orientation matrix O11..O33, eKen11..eKen33, and GrainRadius, plus a strain-stress.csv (two columns: strain, stress). The repo ships mwe_data/ff_calibration/ (9 load steps of 500 grains). For a physically registered fit, first run experiment rotation.
Recipe#
[ ]:
from pathlib import Path
import graintrace as _gt
from graintrace.material_calibration import MaterialCalibration
from graintrace.taylor import TaylorModel
_cpfe_base = str(Path(_gt.__file__).parent / "cpfe_base")
calib = MaterialCalibration(
model_class=TaylorModel,
model_args=dict(
neml2_path=_cpfe_base + "/neml2_cpfe_calibration.i",
npoints=30, nchunk=2, device="cuda", compile=False,
),
data_args=dict(
data_dir="mwe_data/ff_calibration",
strain_stress_file="mwe_data/ff_calibration/strain-stress.csv",
npoints=30, full_field_strain_units="microstrain", straintype="eKen",
max_strain=0.006, n_grains=100, seed=42,
),
save_dir="out/material_calibration",
apply_elastic_correction=False,
strain_window=(0.0, 0.0015),
)
calib.plot_texture(direction=[1, 1, 1])
calib.plot_stress_strain()
calib.calibrate(maxiter=15, lr=0.3, max_iter_per_step=6,
line_search_fn="strong_wolfe",
plateau_rtol=1e-3, plateau_window=2) # guard stops early
calib.load("out/material_calibration/calibrated_material.json")
calib.plot_stress_strain(include_model=True)
calib.plot_strain_histogram(include_initial_strain=True)
Key parameters#
model:
device("cuda"or"cpu"),npoints(pyzag time steps),nchunk(chunk size for the bidiagonal-in-time solve).data:
n_grains(subsample per load step;Nonefor all),max_strain(macro-curve cap),straintype("eKen"or"eFab").calibrate:maxiteris an upper bound; the plateau guard (plateau_rtol,plateau_window) stops early when the relative loss improvement stalls.
Outputs (in save_dir)#
calibrated_material.jsonandautosave_material.json.Pole figures, stress-strain overlays, and elastic-strain histograms.
Map the TaylorModel.opt_vars to CPFE material names to feed the material dict in CPFE simulation: elastic_tensor_E to elastic_E, elastic_tensor_G to elastic_G, elastic_tensor_nu to elastic_nu, slip_strength_constant_strength to slip_constant_strength, voce_hardening_initial_slope, and voce_hardening_saturated_hardening to voce_hardening_saturation.
Gotchas#
CUDA works because
taylor.pymoves the whole nonlinear system withnsys.to(device). A cuda/cpu mismatch surfaces as a silentloss=infand means the model stayed on CPU; do not reintroduce atorch.set_default_devicehack.v3 mixed-control uses an unweighted grain mean. Some parameters can hit reparametrization bounds on small demo configs (few grains, narrow window); use more grains and a wider window for production fits.
Full example#
Full script: examples/demonstrate_material_calibration.py