Tensorial Elasto-Plastic Model (EPM)¶
Quick Reference¶
Use when: Full stress tensor modeling, normal stress differences (\(N_1, N_2\)), anisotropic yielding, flow instabilities
Parameters: 10 fitted (\(\mu, \nu, \tau_{\text{pl,shear}}, \tau_{\text{pl,normal}}, \sigma_{c,\text{mean}}, \sigma_{c,\text{std}}, n_{\text{fluid}}, w_{N1}, H_{\text{Hill}}, N_{\text{Hill}}\)) +
fluidity_form,yield_criterion,L,dtconfigKey equation: \(\partial_t \sigma_{ij} = 2\mu \dot{\varepsilon}^e_{ij} - 2\mu \dot{\varepsilon}^p_{ij} + \sum_{kl} \mathcal{G}_{ij,kl}(\mathbf{q}) \dot{\varepsilon}^p_{kl}\) with \(\dot{\varepsilon}^p_{ij} = (\sigma'_{ij}/\sigma_\text{eff}) \cdot g_\text{eff}(\sigma_\text{eff}, \sigma_c, n_\text{fluid}) / \tau_{ij}^{pl}\)
Test modes: flow_curve, startup, relaxation, creep, oscillation
Material examples: Rod climbing polymer melts, fiber suspensions, anisotropic gels, flow-induced microstructure
Overview¶
The Tensorial EPM extends the scalar Elasto-Plastic Models (EPM) to track the full stress tensor, enabling predictions of normal stress differences (\(N_1, N_2\)), anisotropic yielding, and flow-induced microstructure. This is critical for capturing non-Newtonian behaviors like rod climbing (Weissenberg effect), die swell, and flow instabilities.
Notation Guide¶
Symbol |
Units |
Description |
|---|---|---|
\(\sigma_{ij}\) |
Pa |
Stress tensor components [\(\sigma_{xx}\), \(\sigma_{yy}\), \(\sigma_{xy}\)] |
\(\sigma_{zz}\) |
Pa |
Out-of-plane stress (from plane strain constraint) |
\(N_1\) |
Pa |
First normal stress difference (\(\sigma_{xx} - \sigma_{yy}\)) |
\(N_2\) |
Pa |
Second normal stress difference (\(\sigma_{yy} - \sigma_{zz}\)) |
\(\sigma_{eff}\) |
Pa |
Effective stress (von Mises or Hill criterion) |
\(\dot{\gamma}^p_{ij}\) |
1/s |
Plastic strain rate tensor (deviatoric) |
\(\mathcal{G}_{ij,kl}\) |
— |
Tensorial Eshelby propagator (4th-order) |
\(\nu\) |
— |
Poisson’s ratio (plane strain constraint) |
\(H, N\) |
— |
Hill anisotropy parameters |
Physical Interpretation¶
Tensorial vs Scalar EPM¶
The scalar EPM models only the shear component \(\sigma_xy\). While this captures flow curves and avalanches, it misses:
Normal stress differences: \(N_1 = \sigma_{xx} - \sigma_{yy}\) (rod climbing) and \(N_2 = \sigma_{yy} - \sigma_{zz}\) (secondary flows)
Anisotropic yielding: Materials with directional microstructure (fiber suspensions, liquid crystals)
Flow instabilities: Edge fracture, shear banding driven by normal stress gradients
The Tensorial EPM tracks [\(\sigma_{xx}, \sigma_{yy}, \sigma_{xy}\)] at each lattice site, with \(\sigma_{zz} = \nu(\sigma_{xx} + \sigma_{yy})\) from the plane strain constraint.
Tensorial Eshelby Propagator¶
When a site yields plastically, the stress redistribution couples all components:
This is the tensorial extension of the scalar EPM evolution equation (see Discrete State Variables in Elasto-Plastic Models (EPM)).
The tensorial propagator \(\mathcal{G}_{ij,kl}\) derives from the Eshelby inclusion solution in plane strain:
where C is the elastic stiffness tensor with shear modulus \(\mu\) and Poisson ratio \(\nu\).
Yield Criteria¶
Von Mises (Isotropic):
Hill (Anisotropic):
where H and N are anisotropy parameters (H=1, N=3 recovers von Mises in plane stress).
Note
For protocol-specific governing equations (flow curve, startup, relaxation, creep, oscillation), see the Flow Curve (Rotation / Steady Shear) section in Elasto-Plastic Models (EPM). The tensorial extension uses the same protocol structure with the full stress tensor.
Prandtl-Reuss Flow Rule¶
Plastic flow is component-wise with independent timescales:
where \(\sigma'_{ij}\) is the deviatoric stress. Separate \(\tau_{\text{pl,shear}}\) and \(\tau_{\text{pl,normal}}\) allow modeling materials with different relaxation times for shear and dilation.
Physical Foundations¶
Why Track the Full Stress Tensor?¶
Many complex fluids exhibit normal stress differences during shear flow:
Polymer melts: Rod climbing (Weissenberg effect), die swell, extrudate swell
Fiber suspensions: Normal stresses from fiber orientation and rotation
Anisotropic gels: Directional microstructure leads to non-isotropic yielding
The scalar EPM (\(\sigma_{xy}\) only) misses these phenomena because:
Normal components \(\sigma_{xx}, \sigma_{yy}\) evolve independently under shear
Yielding in one direction affects stress redistribution in all directions
Anisotropic yield criteria (Hill) require full tensor
Tensorial Eshelby Solution¶
When a site yields plastically with strain rate \(\dot{\gamma}^{pl}_{kl}\), the stress redistribution to site \(\mathbf{r}\) is given by the 4th-order Eshelby tensor:
In Fourier space (plane strain, 2D):
where C is the elastic stiffness tensor. For isotropic elasticity with shear modulus \(\mu\) and Poisson ratio \(\nu\):
Key property: The propagator couples all stress components, so a plastic event in \(\sigma_{xy}\) affects \(\sigma_{xx}\) and \(\sigma_{yy}\), and vice versa.
Plane Strain and Normal Stress Generation¶
In 2D flow with out-of-plane confinement (e.g., narrow gap rheometry), the strain \(\varepsilon_{zz} = 0\) but stress \(\sigma_{zz} \neq 0\). The constraint is:
This coupling generates non-zero \(N_2\):
even when \(N_1 = \sigma_{xx} - \sigma_{yy}\) might be small. This is a purely geometric effect of confinement.
Governing Equations¶
Plane Strain Constraint¶
For 2D flow with out-of-plane confinement:
This couples the in-plane components and leads to non-zero \(N_2\).
Normal Stress Differences¶
Typical experimental observations: - Polymer melts: \(N_1 > 0\) (rod climbing), \(|N_2| \ll N_1\) - Shear banding: Large gradients in \(N_1\) correlate with band boundaries
Fitting to Normal Stress Data¶
The loss function for combined fitting is:
Set w_N1 > 1 to prioritize normal stress accuracy.
Note
Combined \([\sigma_{xy}, N_1]\) fitting is not yet wired through
TensorialEPM.fit(); the current fit() supports shear-only 1D
\(\sigma_{xy}\) targets and delegates to EPMBase._fit. For now,
w_N1 is reserved for future use. You can still predict \(N_1\)
from a shear-only fit via model.predict(..., test_mode="flow_curve")
which returns \(N_1\) in result.metadata["N1"].
Constitutive Forms and the von Mises / Tensorial Conventions¶
The tensorial kernel supports the same three fluidity_form options as
the scalar Elasto-Plastic Models (EPM) (see Constitutive Forms for the Plastic Strain Rate), applied
component-wise through the Prandtl–Reuss flow direction:
where \(\sigma'_{ij}\) is the deviatoric stress, \(\sigma_\text{eff}\)
is the yield-criterion effective stress, \(\tau^{pl}_{ij}\) is
tau_pl_shear for \(ij = xy\) and tau_pl_normal otherwise, and
\(g_\text{eff}\) is the form-dependent magnitude factor shared with the
scalar kernel.
Two subtleties distinguish the tensorial case from the scalar kernel and both affect how you interpret fitted parameters:
1. The \(\sqrt{3}\) factor from von Mises.
For pure shear the von Mises effective stress is
\(\sigma_\text{eff} = \sqrt{3}\,|\sigma_{xy}|\), so yielding begins at
\(|\sigma_{xy}| = \sigma_{c,\text{mean}}/\sqrt{3}\) — a factor of
\(\sqrt{3}\) below the scalar sigma_c_mean. The macroscopic yield
stress you’d measure from the tensorial flow curve is
so to match a measured emulsion yield stress \(\sigma_y \approx 24\) Pa,
set the tensorial sigma_c_mean to \(24\sqrt{3} \approx 41.6\) Pa.
The scalar LatticeEPM has no such factor — there, sigma_c_mean directly
equals \(\sigma_y\).
2. The factor-of-2 in the stress-rate convention.
The tensorial stress update follows the Budrikis–Zapperi 2013 convention
\(d\sigma_{ij}/dt = 2\mu\,\dot\varepsilon^e_{ij} - 2\mu\,\dot\varepsilon^p_{ij} + \mathcal{G}\,\dot\varepsilon^p\),
so at steady state under imposed shear rate \(\dot\gamma\) the tensor
plastic strain rate must equal \(\dot\gamma/2\) (half the engineering
shear rate). This changes the analytical flow-curve formula for the
"overstress" form:
Special cases:
\(n_{\text{fluid}} = 1\): \(\sigma_{xy} = \sigma_{c,\text{mean}}/\sqrt{3} + \dot\gamma\,\tau_{pl,\text{shear}}/2\) (Bingham with yield plateau)
\(n_{\text{fluid}} = 2\): \(\sigma_{xy} = \sigma_{c,\text{mean}}/\sqrt{3} + \sqrt{\sigma_{c,\text{mean}}\,\dot\gamma\,\tau_{pl,\text{shear}}/(2\sqrt{3})}\) (HB with \(n_{HB} = 0.5\))
These formulas are what the warm-start in
TensorialEPM._run_flow_curve uses to seed each shear rate’s simulation
before stepping, so the flow curve converges to the HB ceiling without
long transients.
3. Mapping scalar fit parameters to tensorial initial guesses. When you have a good scalar LatticeEPM fit and want to initialize the tensorial model, apply the two corrections above:
import numpy as np
sqrt3 = np.sqrt(3.0)
# scalar_fit is a dict of fitted LatticeEPM parameters (overstress form)
tensor_init = {
"mu": 1.0, # decoupled — pure shear doesn't depend on mu
"tau_pl_shear": 2.0 * scalar_fit["tau_pl"], # factor of 2 from tensor Hooke's law
"tau_pl_normal": 2.0 * scalar_fit["tau_pl"],
"sigma_c_mean": sqrt3 * scalar_fit["sigma_c_mean"], # von Mises plateau scaling
"sigma_c_std": 0.05 * scalar_fit["sigma_c_mean"] * sqrt3,
"n_fluid": scalar_fit["n_fluid"], # same HB exponent
}
model_tensor = TensorialEPM(
L=32, dt=0.01, nu=0.48,
fluidity_form="overstress",
**tensor_init,
)
With these mappings the tensorial fit reaches the same Herschel–Bulkley ceiling as the scalar fit (typical \(R^2_{\text{lin}} > 0.993\), max relative error \(< 10\%\) on the \(\phi = 0.80\) emulsion).
Validity and Assumptions¶
Valid for:
Materials where normal stresses are measurable and significant (\(N_1/\sigma_{xy} > 0.1\))
Anisotropic materials with directional microstructure (fibers, liquid crystals)
Flow instabilities driven by normal stress gradients (shear banding, edge fracture)
Confined geometries where plane strain applies (narrow gap, slit flow)
Assumptions:
Plane strain constraint: \(\varepsilon_{zz} = 0\) (appropriate for 2D confined flow)
Isotropic elasticity (unless Hill criterion used for anisotropy)
Quenched disorder in yield thresholds (same as scalar EPM)
No inertia (overdamped dynamics)
Not appropriate for:
Pure shear measurements where \(N_1\) is not measured (use
LatticeEPMinstead)3D bulk flows without confinement (requires full 3D tensor implementation)
Very compressible materials (model assumes \(\nu \approx 0.4\text{--}0.5\))
What You Can Learn¶
From fitting TensorialEPM to experimental data, you can extract insights about normal stress generation, anisotropic yielding, and flow instabilities in soft matter.
Parameter Interpretation¶
- \(\sigma_c\) (Yield Stress Threshold):
Local critical stress for plastic yielding in von Mises or Hill criterion. For graduate students: For von Mises, \(\sigma_{\text{eff}} = \sqrt{\frac{1}{2}\boldsymbol{\tau}:\boldsymbol{\tau}}\) must exceed \(\sigma_c\) for plastic flow. Connects to microscopic cage breaking or bond rupture energetics. For practitioners: Design parameter for processing stresses. \(\sigma_c\) sets minimum stress for continuous flow.
- \(N_1, N_2\) (Normal Stress Differences):
First (\(N_1 = \sigma_{xx} - \sigma_{yy}\)) and second (\(N_2 = \sigma_{yy} - \sigma_{zz}\)) normal stress differences from tensorial stress state. For graduate students: \(N_1\) arises from upper-convected Maxwell backbone (chain stretching, particle alignment). \(N_2\) from plane strain constraint: \(N_2 = (1-\nu)\sigma_{yy} - \nu\sigma_{xx}\). Ratio \(N_1/\sigma_{xy} \sim \text{Wi}\) (Weissenberg number) quantifies elasticity. For practitioners: Measure \(N_1\) to predict rod climbing (Weissenberg effect), die swell, and edge fracture. \(N_1/\sigma_{xy} > 0.5\) indicates strong elastic effects.
- Hill H, N (Anisotropy Parameters):
Hill criterion parameters quantifying directional yield resistance (H for normal, N for shear). For graduate students: Effective stress: \(\sigma_{\text{eff,Hill}} = \sqrt{H(\sigma_{xx}-\sigma_{yy})^2 + 2N\sigma_{xy}^2}\). \(H=1, N=3\) recovers von Mises. Microstructurally, \(H\) and \(N\) relate to fiber orientation tensor or crystallographic texture. For practitioners: Fit H and N from biaxial or combined loading tests. Use to predict forming limits and failure modes in anisotropic materials.
Material Classification¶
Parameter Range |
Material Behavior |
Typical Materials |
Processing Implications |
|---|---|---|---|
\(N_1/\sigma_{xy} < 0.1\) |
Weakly elastic |
Pastes, concentrated suspensions |
Minimal normal stress effects |
\(N_1/\sigma_{xy} = 0.1\text{--}1\) |
Moderate elasticity |
Emulsions, soft colloids |
Rod climbing, moderate die swell |
\(N_1/\sigma_{xy} > 1\) |
Strongly elastic |
Polymer melts, fiber suspensions |
Strong Weissenberg effect, edge fracture |
H=1, N=3 (isotropic) |
von Mises yielding |
Isotropic gels, foams |
Symmetric flow patterns |
\(H \neq 1\) or \(N \neq 3\) (anisotropic) |
Directional yielding |
Fiber composites, liquid crystals |
Asymmetric instabilities, orientation-dependent strength |
Fitting Guidance¶
Initialization Strategy¶
Step 1: Fit shear stress only (w_N1 = 0)
Start with shear stress data to get baseline parameters:
mu,sigma_c_mean,tau_pl_shear
This is equivalent to scalar EPM fitting.
Step 2: Add normal stress constraint (w_N1 = 1)
Refine parameters to match both \(\sigma_{xy}\) and \(N_1\):
Adjust
nu(Poisson ratio) to control \(N_1\) magnitudeAdjust
tau_pl_normalif \(N_1\) relaxation differs from shear
Step 3: Test anisotropy (Hill criterion)
If isotropic fit fails (\(R^2 < 0.9\) for \(N_1\)):
Switch to
yield_criterion='hill'Fit
hill_Handhill_Nwhile holding other parameters fixed
Parameter Bounds and Physical Constraints¶
Parameter |
Typical Range |
Physical Constraint |
|---|---|---|
|
0.40-0.49 |
Avoid 0.5 (incompressible singularity); \(N_1\) sensitive to \(\nu\) |
|
0.01-10 s |
Match shear stress relaxation timescale |
|
0.1-10× tau_pl_shear |
Often similar, but can differ for anisotropic materials |
|
0.1-10 |
Higher weight = prioritize \(N_1\) fit over \(\sigma_{xy}\) |
|
0.5-2.0 |
H = 1, N = 3 recovers von Mises (isotropic) |
|
1.5-5.0 |
N controls shear-normal coupling |
Common Fitting Issues¶
Issue |
Solution |
|---|---|
\(N_1\) predictions too small |
Increase |
\(N_1\) predictions too large |
Increase |
Shear fit good, \(N_1\) fit poor |
Increase |
Convergence fails with w_N1 > 0 |
Fit shear first (w_N1=0), then refine with w_N1=1 |
GPU memory overflow |
Reduce |
API Reference¶
- class rheojax.models.epm.tensor.TensorialEPM(L=64, dt=0.01, mu=1.0, nu=0.48, tau_pl=1.0, tau_pl_shear=1.0, tau_pl_normal=1.0, sigma_c_mean=1.0, sigma_c_std=0.1, n_fluid=1.0, yield_criterion='von_mises', n_bayesian_steps=200, fluidity_form='overstress')[source]
Bases:
EPMBase3-Component Tensorial Lattice EPM.
A mesoscopic model for amorphous solids that explicitly tracks the full stress tensor, enabling predictions of normal stress differences and anisotropic flow.
- Physics:
Lattice of elastoplastic blocks with tensorial stress state.
Elastic loading (affine) for all stress components.
Von Mises or Hill yield criterion for anisotropic materials.
Component-wise plastic flow rule (Prandtl-Reuss).
Tensorial Eshelby propagator for stress redistribution.
- Parameters:
mu (
float) – Shear modulus. Default 1.0.nu (
float) – Poisson’s ratio for plane strain. Default 0.48 (avoid 0.5 singularity).tau_pl (
float) – Base plastic relaxation timescale (legacy). Default 1.0.tau_pl_shear (
float) – Plastic relaxation time for shear. Default 1.0.tau_pl_normal (
float) – Plastic relaxation time for normal stresses. Default 1.0.sigma_c_mean (
float) – Mean yield threshold. Default 1.0.sigma_c_std (
float) – Disorder strength (std dev of thresholds). Default 0.1.smoothing_width (float) – Width for smooth yielding approx (inference only). Default 0.1.
w_N1 (float) – Weight for N₁ in combined fitting loss. Default 1.0.
hill_H (float) – Hill anisotropy parameter H. Default 0.5.
hill_N (float) – Hill anisotropy parameter N. Default 1.5.
- Configuration:
L (int): Lattice size (LxL). Default 64. dt (float): Time step. Default 0.01. yield_criterion (str): “von_mises” or “hill”. Default “von_mises”.
- __init__(L=64, dt=0.01, mu=1.0, nu=0.48, tau_pl=1.0, tau_pl_shear=1.0, tau_pl_normal=1.0, sigma_c_mean=1.0, sigma_c_std=0.1, n_fluid=1.0, yield_criterion='von_mises', n_bayesian_steps=200, fluidity_form='overstress')[source]
Initialize the Tensorial EPM.
- Parameters:
L (
int) – Lattice size (LxL grid).dt (
float) – Time step for integration.mu (
float) – Shear modulus.nu (
float) – Poisson’s ratio for plane strain constraint.tau_pl (
float) – Base plastic relaxation time (for compatibility).tau_pl_shear (
float) – Plastic relaxation time for shear components.tau_pl_normal (
float) – Plastic relaxation time for normal stress components.sigma_c_mean (
float) – Mean yield threshold.sigma_c_std (
float) – Standard deviation of yield thresholds (disorder).yield_criterion (
str) – Yield criterion name (“von_mises” or “hill”).n_bayesian_steps (
int) – Number of time steps for Bayesian inference. Default 200.
- get_shear_stress(stress)[source]
Extract shear stress component from stress tensor.
- get_normal_stress_differences(stress, nu=None)[source]
Compute normal stress differences from stress tensor.
For plane strain: σ_zz = ν(σ_xx + σ_yy)
Normal stress differences: - N₁ = σ_xx - σ_yy - N₂ = σ_yy - σ_zz
- predict_normal_stresses(data, **kwargs)[source]
Convenience method to predict normal stress differences.
Runs the simulation and extracts N₁ and N₂ spatial averages over time.
- Parameters:
data (
RheoData) – RheoData with protocol specification.**kwargs – Additional arguments passed to predict().
- Return type:
- Returns:
Tuple (N₁_array, N₂_array) with time-averaged values.
- Raises:
NotImplementedError – Not yet implemented (future feature).
Parameters¶
Parameter |
Units |
Bounds |
Description |
|---|---|---|---|
|
Pa |
[0.1, 1e9] |
Shear modulus (elastic stiffness) |
|
dimensionless |
[0.3, 0.5] |
Poisson’s ratio (plane strain), avoid 0.5 (incompressible singularity) |
|
s |
[0.01, 100.0] |
Plastic relaxation time for shear components (\(\sigma_{xy}\)) |
|
s |
[0.01, 100.0] |
Plastic relaxation time for normal stresses (\(\sigma_{xx}, \sigma_{yy}\)) |
|
Pa |
[0.1, 1e6] |
Mean local yield threshold. Important for the tensorial kernel: the pure-shear plateau is at \(\sigma_{c,\text{mean}}/\sqrt{3}\) (a factor of \(\sqrt{3}\) below the scalar LatticeEPM) due to the von Mises yield criterion \(\sigma_\text{eff} = \sqrt{3}\,|\sigma_{xy}|\). To match a measured yield stress \(\sigma_y\), use \(\sigma_{c,\text{mean}} = \sigma_y \cdot \sqrt{3}\). |
|
Pa |
[0.0, 100.0] |
Disorder strength (std dev of thresholds) |
|
dimensionless |
[0.5, 5.0] |
Power-law / HB exponent of the plastic strain rate law. Implied HB
shear-thinning exponent is \(n_{HB} = 1/n_{\text{fluid}}\). At
\(n_{\text{fluid}}=1\) the |
|
dimensionless |
[0.1, 10.0] |
Weight for \(N_1\) in combined fitting loss (reserved for future
combined \([\sigma_{xy}, N_1]\) fit; currently |
|
dimensionless |
[0.1, 5.0] |
Hill anisotropy parameter H (normal stress coupling). Works with any
|
|
dimensionless |
[0.1, 5.0] |
Hill anisotropy parameter N (shear amplification). At \(H=0.5, N=1.5\) Hill reduces exactly to von Mises for pure shear. |
Configuration (not fitted):
Parameter |
Default |
Description |
|---|---|---|
|
64 |
Lattice size (L×L grid) |
|
0.01 |
Time step for integration |
|
|
Yield criterion: |
|
|
Plastic strain rate constitutive law. Same three values as scalar
LatticeEPM: |
|
0 |
Random seed for threshold initialization (reproducibility) |
Usage¶
Basic Usage¶
from rheojax.models.epm import TensorialEPM
import numpy as np
# Create model instance
model = TensorialEPM(L=32, dt=0.01)
# Fit to flow curve data
gamma_dot = np.logspace(-2, 1, 10)
stress_exp = np.array([0.5, 1.2, 2.8, 5.1, 8.7, 13.5, 19.8, 27.3, 36.2, 46.5])
model.fit(gamma_dot, stress_exp, test_mode='flow_curve')
# Predict stress (including normal stress differences)
gamma_dot_new = np.logspace(-2, 1, 30)
sigma_pred = model.predict(gamma_dot_new, test_mode='flow_curve')
Advanced Usage Examples¶
Basic Flow Curve with \(N_1\) Prediction¶
from rheojax.models.epm.tensor import TensorialEPM
from rheojax.core.data import RheoData
import numpy as np
# Initialize model
model = TensorialEPM(L=64, dt=0.01, mu=1.0, nu=0.48)
# Define shear rates
shear_rates = np.logspace(-2, 1, 10)
data = RheoData(x=shear_rates, y=None, initial_test_mode='flow_curve')
# Predict flow curve (returns σ_xy with N₁ in metadata)
result = model.predict(data, smooth=True)
print(f"Shear stress: {result.y}")
print(f"Normal stress N₁: {result.metadata['N1']}")
Fitting to Shear-Only Data (Herschel–Bulkley flow curves)¶
TensorialEPM.fit() is implemented via rheojax.models.epm.base.EPMBase.fit
and supports shear-only 1D \(\sigma_{xy}\) targets. Under the hood it runs NLSQ
(trust region reflective) against the _model_function_general forward kernel with
smooth=True tanh activation for gradient flow. The default constitutive law is
fluidity_form="overstress", which gives a full Herschel–Bulkley flow curve at the
NLSQ optimum:
import numpy as np
from rheojax.models.epm.tensor import TensorialEPM
# Experimental flow curve (example: concentrated emulsion)
gamma_dot = np.array([0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0])
sigma_xy_exp = np.array([24.0, 24.5, 27.0, 34.0, 55.0, 110.0, 270.0])
# Pick a physically motivated initial guess.
# Remember: tensorial plateau = sigma_c_mean / sqrt(3) due to von Mises,
# so set sigma_c_mean = sigma_y_target * sqrt(3).
sqrt3 = np.sqrt(3.0)
sigma_y_target = float(sigma_xy_exp.min()) # ~24 Pa plateau
model = TensorialEPM(
L=16, dt=0.01, # L=16 for fit speed; 32-64 for production
mu=1.0, nu=0.48,
tau_pl=2.0, tau_pl_shear=2.0, tau_pl_normal=2.0,
sigma_c_mean=sigma_y_target * sqrt3, # von Mises scaling
sigma_c_std=0.5,
n_fluid=2.1, # HB exponent ~ 0.48
fluidity_form="overstress", # default
)
# Widen bounds so NLSQ can explore
model.parameters["mu"].bounds = (0.01, 100.0)
model.parameters["tau_pl_shear"].bounds = (0.001, 100.0)
model.parameters["sigma_c_mean"].bounds = (0.1, 1000.0)
model.parameters["sigma_c_std"].bounds = (0.0001, 1000.0)
model.parameters["n_fluid"].bounds = (0.5, 5.0)
# Fit to shear stress (use_log_residuals is essential for multi-decade gdot data)
model.fit(gamma_dot, sigma_xy_exp, test_mode='flow_curve', use_log_residuals=True)
# Read back the physical HB parameters
scm_fit = float(model.parameters.get_value("sigma_c_mean"))
nf_fit = float(model.parameters.get_value("n_fluid"))
tau_fit = float(model.parameters.get_value("tau_pl_shear"))
print(f"Tensorial fit → sigma_y = sigma_c_mean / sqrt(3) = {scm_fit/sqrt3:.2f} Pa")
print(f" HB exponent n_HB = 1/n_fluid = {1/nf_fit:.3f}")
print(f" tau_pl_shear = {tau_fit:.3f} s")
Note
TensorialEPM.fit()only accepts 1D \(\sigma_{xy}\) data. Passing a 2D target with shape(2, n)for combined \([\sigma_{xy}, N_1]\) fitting raisesNotImplementedError— that mode is reserved for a future release. For now, fit on \(\sigma_{xy}\) and inspect \(N_1\) frommodel.predict(...).metadata["N1"].Tensorial fits are ~5–10× slower than scalar LatticeEPM fits per NLSQ iteration because each forward call runs a full 3-component FFT kernel. Use
L=16and data subsampling during fitting; upgrade toL=32-64only for final validation and production simulations.The scalar LatticeEPM fit parameters are not directly transferable to TensorialEPM — apply the \(\sqrt{3}\) scaling on
sigma_c_meanand the factor-of-2 scaling ontau_pl_shearfirst. See theMapping scalar fit parameters to tensorial initial guessescode block in the Constitutive Forms section above.
Fitting to Combined [\(\sigma_xy, N_1\)] Data¶
# Experimental data with normal stresses
gamma_dot = np.array([0.1, 1.0, 10.0])
sigma_xy_exp = np.array([1.2, 2.8, 5.1])
N1_exp = np.array([0.3, 1.5, 4.2]) # N₁ typically ~ σ_xy in magnitude
# Combine data (requires custom fitting workflow)
# Option 1: Use metadata to pass N₁ targets (future feature)
# Option 2: Manually construct multi-objective loss
# Current best practice: Fit shear first, then validate N₁
rheo_data = RheoData(x=gamma_dot, y=sigma_xy_exp, initial_test_mode='flow_curve')
model = TensorialEPM(L=32, w_N1=2.0) # Higher weight for N₁
model.fit(rheo_data, max_iter=200)
# Check N₁ predictions
pred_result = model.predict(rheo_data)
N1_pred = pred_result.metadata["N1"]
print(f"N₁ RMSE: {np.sqrt(np.mean((N1_pred - N1_exp)**2)):.3f}")
Comparison of Yield Criteria¶
# Von Mises (isotropic)
model_vm = TensorialEPM(L=32, yield_criterion="von_mises")
model_vm.fit(rheo_data)
# Hill (anisotropic)
model_hill = TensorialEPM(L=32, yield_criterion="hill")
model_hill.params.set_value("hill_H", 1.5) # Stronger normal coupling
model_hill.params.set_value("hill_N", 2.0) # Weaker shear resistance
model_hill.fit(rheo_data)
# Compare predictions
pred_vm = model_vm.predict(rheo_data)
pred_hill = model_hill.predict(rheo_data)
Visualization¶
from rheojax.visualization.epm_plots import (
plot_tensorial_fields,
plot_von_mises_field,
plot_normal_stress_ratio,
)
# Run a startup simulation to get stress field history
t = np.linspace(0, 10, 100)
startup_data = RheoData(x=t, y=None, initial_test_mode='startup')
startup_data.metadata = {"gamma_dot": 1.0}
result = model.predict(startup_data, smooth=False)
# Plot tensorial stress components (from history if saved)
# Note: Access to intermediate stress fields requires history tracking
# See examples/tensorial_epm_visualization_demo.py
When to Use TensorialEPM vs LatticeEPM¶
Use Case |
LatticeEPM (Scalar) |
TensorialEPM |
|---|---|---|
Flow curves (\(\sigma\) vs \(\dot{\gamma}\)) |
✓ Faster (3-5x) |
✓ More accurate if \(N_1 \neq 0\) |
Yield stress determination |
✓ Sufficient |
✓ Accounts for anisotropy |
Normal stress differences |
✗ |
✓ Required |
Shear banding analysis |
~ Qualitative |
✓ Quantitative (\(N_1\) gradients) |
Rod climbing / die swell |
✗ |
✓ Required |
Anisotropic materials |
✗ |
✓ Hill criterion |
Computational cost |
1x (baseline) |
3-5x (9 propagator components vs 1) |
Memory usage |
1x |
3x (stress tensor storage) |
Recommendation: Start with LatticeEPM for flow curve fitting. Use TensorialEPM when:
Normal stress data is available
Material shows strong anisotropy (e.g., fiber suspensions)
Analyzing flow instabilities (shear banding, edge fracture)
Modeling 3D extrusion flows
Troubleshooting¶
\(N_1\) Predictions Are Too Small¶
Cause: Insufficient disorder (\(\sigma_{c,\text{std}}\) too low) or \(\nu\) too high (approaching incompressible limit)
Fix: Increase
sigma_c_stdto 0.2-0.5 or reducenuto 0.40-0.45
Fitting Fails to Converge¶
Cause: Combined [\(\sigma_xy, N_1\)] loss has competing gradients
Fix: Fit shear first with
w_N1=0, then refine withw_N1 > 0
Von Mises vs Hill Give Similar Results¶
Cause: Hill parameters (H, N) default to isotropic values
Fix: Set
hill_H\(\neq\) 1 orhill_N\(\neq\) 3 to activate anisotropy
GPU Memory Overflow¶
Cause: Large L or long simulation times
Fix: Reduce
Lto 32 or 48 for fitting, useL=64+only for production simulations
References¶
See Also¶
Elasto-Plastic Models (EPM) — EPM family overview and comparison
Elasto-Plastic Models (EPM) — Scalar EPM for faster fitting when \(N_1\) data unavailable
SGR Conventional (Soft Glassy Rheology) — Handbook — Mean-field SGR (complementary theory)
Hébraud–Lequeux (HL) Model — Handbook — Mean-field HL (EPM limiting case)
rheojax.visualization.epm_plots.plot_tensorial_fields()— Visualization functions for tensor fields
Bayesian Inference for Tensorial EPM¶
Tensorial EPM supports the full NLSQ → NUTS Bayesian pipeline for uncertainty quantification of the extended parameter set.
NLSQ → NUTS Workflow¶
from rheojax.models import TensorialEPM
from rheojax.core.data import RheoData
import numpy as np
# Load experimental data with normal stresses
gamma_dot = np.logspace(-2, 1, 20)
sigma_xy_exp = np.array([...]) # Shear stress
N1_exp = np.array([...]) # First normal stress difference
# Create model with tensor capability
model = TensorialEPM(
L=32, # Moderate lattice for fitting
dt=0.01,
yield_criterion="von_mises",
w_N1=2.0 # Weight for N₁ in combined loss
)
# Step 1: NLSQ fitting for point estimates
rheo_data = RheoData(x=gamma_dot, y=sigma_xy_exp, initial_test_mode='flow_curve')
model.fit(rheo_data, max_iter=300)
# Step 2: Bayesian inference with 4 chains
result = model.fit_bayesian(
rheo_data,
test_mode='flow_curve',
num_warmup=1000,
num_samples=2000,
num_chains=4,
seed=42
)
# Step 3: Analyze posteriors and diagnostics
print(result.summary)
print(f"R-hat (max): {max(result.diagnostics['r_hat'].values()):.4f}")
print(f"ESS (min): {min(result.diagnostics['ess'].values()):.0f}")
print(f"Divergences: {result.diagnostics['divergences']:.1%}")
Prior Specification for Tensor Parameters¶
TensorialEPM uses weakly informative priors tailored to the tensor structure:
Parameter |
Prior Distribution |
Rationale |
|---|---|---|
\(\mu\) (shear modulus) |
LogNormal(log(10), 1.5) |
Positive, spans 0.1-1000 Pa |
\(\nu\) (Poisson ratio) |
Beta(8, 2) scaled to [0.3, 0.5] |
Constrained near incompressible |
\(\sigma_{c,\text{mean}}\) |
HalfNormal(10) |
Positive, order of yield stress |
\(\sigma_{c,\text{std}}\) |
HalfNormal(2) |
Positive, smaller than mean |
\(\tau_{\text{pl,shear}}\) |
LogNormal(log(1), 1) |
Positive, log-uniform over decades |
\(\tau_{\text{pl,normal}}\) |
LogNormal(log(1), 1) |
Often similar to shear, but free |
w_N1 |
Fixed (not sampled) |
User-specified weight |
hill_H |
HalfNormal(1) |
Centered on isotropic (H=1) |
hill_N |
HalfNormal(3) |
Centered on von Mises (N=3) |
Customizing priors:
from numpyro import distributions as dist
custom_priors = {
"nu": dist.Uniform(0.40, 0.48), # Tighter constraint on ν
"tau_pl_normal": dist.LogNormal(
loc=jnp.log(model.params.get_value("tau_pl_shear")),
scale=0.5 # Prior centered on shear value
),
}
result = model.fit_bayesian(
rheo_data,
priors=custom_priors,
num_warmup=1000,
num_samples=2000,
num_chains=4
)
Multi-Chain Diagnostics for Tensor Models¶
Tensorial EPM posteriors can exhibit complex geometry due to parameter correlations. Use 4 chains (default) and check diagnostics carefully:
Diagnostic |
Target |
Action if Failed |
|---|---|---|
R-hat (all params) |
< 1.05 |
Increase num_samples, check for multimodality |
ESS (all params) |
> 400 |
Increase num_samples or reduce correlation |
ESS bulk/tail ratio |
> 0.5 |
Indicates chain mixing issues |
Divergences |
< 1% |
Reduce step size, reparameterize |
BFMI (energy) |
> 0.3 |
Indicates sampling difficulties |
Common parameter correlations:
\(\nu\) ↔ \(\sigma_{c,\text{std}}\): Higher Poisson ratio compensates for lower disorder
\(\tau_{\text{pl,shear}}\) ↔ \(\tau_{\text{pl,normal}}\): Often correlated; consider fixing ratio
hill_H ↔ hill_N: Strong correlation in anisotropic fits
import arviz as az
# Convert to ArviZ format
idata = result.to_arviz()
# Pair plot to visualize correlations
az.plot_pair(
idata,
var_names=["nu", "sigma_c_std", "tau_pl_shear", "tau_pl_normal"],
divergences=True,
figsize=(10, 10)
)
# Energy plot (BFMI diagnostic)
az.plot_energy(idata)
# Rank plot (chain mixing)
az.plot_rank(idata, var_names=["nu", "sigma_c_mean"])
Combined [\(\sigma_xy, N_1\)] Fitting Strategy¶
Fitting to both shear and normal stress data requires careful balancing:
Strategy 1: Sequential fitting
Fit \(\sigma_{xy}\) only (w_N1 = 0) to get \(\mu, \sigma_c, \tau_{\text{pl}}\)
Fix shear parameters, fit \(\nu, \tau_{\text{pl,normal}}\) from \(N_1\)
Joint refinement with w_N1 = 1
# Step 1: Shear-only fit
model_shear = TensorialEPM(L=32, w_N1=0)
model_shear.fit(rheo_data, max_iter=200)
# Step 2: Fix shear params, fit normal
model_normal = TensorialEPM(L=32, w_N1=10)
model_normal.params = model_shear.params.copy()
model_normal.params.fix("mu")
model_normal.params.fix("sigma_c_mean")
model_normal.fit(rheo_data_with_N1, max_iter=100)
# Step 3: Joint Bayesian
model_joint = TensorialEPM(L=32, w_N1=2)
model_joint.params = model_normal.params.copy()
model_joint.params.unfix_all()
result = model_joint.fit_bayesian(
rheo_data_with_N1,
num_warmup=1000,
num_samples=2000,
num_chains=4
)
Strategy 2: Weighted joint fitting
# Adjust w_N1 based on data quality
# Higher weight if N₁ data is high-quality
# Lower weight if N₁ is noisy
model = TensorialEPM(L=32, w_N1=2.0)
# Bayesian with both observables
result = model.fit_bayesian_multi_observable(
gamma_dot=gamma_dot,
sigma_xy=sigma_xy_exp,
N1=N1_exp,
weights={"sigma_xy": 1.0, "N1": 2.0},
num_warmup=1000,
num_samples=2000,
num_chains=4
)
Advanced Normal Stress Physics¶
This section provides deeper insight into the physical origins and interpretation of normal stress differences in TensorialEPM.
Microscopic Origin of Normal Stresses¶
Normal stress differences arise from microstructural anisotropy induced by shear:
In colloidal systems:
Particles align along the compression axis (45° to flow)
Cage distortion creates asymmetric stress distribution
\(N_1 > 0\): Extension along flow direction dominates
In polymer melts:
Chain stretching and orientation along flow
Entropic elasticity: stretched chains exert restoring force
Classic result: \(N_1 \approx 2G'\gamma\) for linear regime
In fiber suspensions:
Fiber orientation tensor evolves under shear
Jeffery orbits create periodic normal stress oscillations
Steady-state \(N_1\) depends on aspect ratio and concentration
Rate-Dependence of Normal Stresses¶
The ratio \(N_1/\sigma_{xy}\) typically shows characteristic rate-dependence:
Low shear rates (linear regime):
In this regime, \(N_1/\sigma_{xy} \sim \text{Wi}\) (Weissenberg number).
Intermediate rates (non-linear):
Power-law scaling with exponent depending on microstructure.
High shear rates (shear thinning):
Plateau ratio indicates fully aligned microstructure.
Diagnostic: \(N_1/\sigma_{xy}\) ratio
def diagnose_normal_stress_ratio(gamma_dot, sigma_xy, N1):
"""Diagnose material behavior from N₁/σ_xy ratio."""
ratio = N1 / sigma_xy
# Weissenberg number interpretation
Wi = ratio # In linear regime
# Regime detection
if np.mean(ratio) < 0.1:
regime = "weakly elastic (colloidal suspension)"
elif np.mean(ratio) < 1.0:
regime = "moderate elasticity (emulsion, soft colloid)"
else:
regime = "strongly elastic (polymer melt, fiber suspension)"
# Rate dependence exponent
from scipy import stats
log_gd = np.log10(gamma_dot)
log_ratio = np.log10(ratio)
slope, _, r_value, _, _ = stats.linregress(log_gd, log_ratio)
return {
"mean_ratio": np.mean(ratio),
"regime": regime,
"rate_exponent": slope,
"linearity_r2": r_value**2
}
Anisotropic Yielding: Hill Criterion Details¶
The Hill criterion extends von Mises to anisotropic yield behavior, essential for materials with directional microstructure.
Hill Criterion Derivation¶
For materials with principal axes of anisotropy, Hill (1948) proposed:
In plane stress (\(\sigma_{zz} = \sigma_{xz} = \sigma_{yz} = 0\)):
Parameter interpretation:
H: Resistance to normal stress difference (\(\sigma_{xx} - \sigma_{yy}\))
N: Resistance to shear stress \(\sigma_{xy}\)
H = 1, N = 3: Recovers von Mises (isotropic)
\(H > 1\): Stronger in normal direction (resists \(N_1\) generation)
\(N > 3\): Stronger in shear (higher yield stress for same \(\sigma_{xy}\))
Connection to Fiber Orientation Tensor¶
For fiber suspensions, Hill parameters relate to the orientation tensor a:
where p is the fiber orientation unit vector and c_H, c_N are material constants.
Fully aligned fibers (p_x = 1): H > 1 (strong normal resistance), yield is dominated by shear
Random orientation: H ≈ 1, N ≈ 3 (recovers von Mises)
Visualization: Yield Surfaces¶
import numpy as np
import matplotlib.pyplot as plt
def plot_yield_surface(H=1.0, N=3.0, sigma_c=1.0):
"""Plot Hill yield surface in (σ_xx - σ_yy, σ_xy) space."""
theta = np.linspace(0, 2*np.pi, 100)
# Parametric form of yield surface
# H(σ_xx - σ_yy)² + 2N σ_xy² = σ_c²
sigma_normal = sigma_c * np.cos(theta) / np.sqrt(H)
sigma_shear = sigma_c * np.sin(theta) / np.sqrt(2*N)
plt.figure(figsize=(6, 6))
plt.plot(sigma_normal, sigma_shear, 'b-', linewidth=2,
label=f'Hill (H={H}, N={N})')
# Reference: von Mises (H=1, N=3)
sigma_normal_vm = sigma_c * np.cos(theta)
sigma_shear_vm = sigma_c * np.sin(theta) / np.sqrt(6)
plt.plot(sigma_normal_vm, sigma_shear_vm, 'k--', alpha=0.5,
label='von Mises')
plt.xlabel(r'$\sigma_{xx} - \sigma_{yy}$ (Pa)')
plt.ylabel(r'$\sigma_{xy}$ (Pa)')
plt.axis('equal')
plt.legend()
plt.title('Hill Yield Surface')
plt.grid(True, alpha=0.3)
return plt.gcf()
Enhanced Experimental Protocols¶
This section details experimental considerations for obtaining high-quality data suitable for TensorialEPM fitting.
Normal Stress Measurement Techniques¶
Cone-plate geometry:
Preferred for \(N_1\) measurement (uniform shear rate)
Edge fracture limits high rates (\(N_1 \sim \sigma_{xy}\) at critical Wi)
Requires thrust bearing calibration
Parallel plates:
\(N_1\) requires correction: \(N_1 = 2F_N/(\pi R^2)(1 + d \ln F_N / d \ln \dot{\gamma})\)
Gap variation affects accuracy
Useful for temperature sweeps
Capillary rheometry:
\(N_1\) from exit pressure (Bagley plot)
High shear rates accessible
End corrections essential
Geometry |
\(N_1\) Accuracy |
Rate Range |
Limitations |
|---|---|---|---|
Cone-plate |
Excellent |
10\(^{-3 - 10^2}\) 1/s |
Edge fracture at high Wi |
Parallel plate |
Good (with correction) |
10\(^{-2 - 10^3}\) 1/s |
Gap-dependent correction |
Capillary |
Moderate |
\(10^1 - 10^5\) 1/s |
End effects, indirect |
Shear Banding Detection¶
TensorialEPM can capture shear banding driven by normal stress gradients. Detection methods:
RheoJAX-PIV (Particle Image Velocimetry):
Direct velocity profile measurement
Banding: linear profile with sharp gradient
Resolution: ~10 \(\mu\text{m}\) typical
RheoJAX-SANS (Small-Angle Neutron Scattering):
Microstructure orientation during flow
Bands have different alignment signatures
Requires contrast-matched samples
Stress fluctuations:
Banding creates stress fluctuations at band boundaries
Monitor \(N_1\) noise: high variance indicates instability
Data Quality Checklist¶
Before fitting TensorialEPM, verify data quality:
Check |
Requirement |
|---|---|
Steady state reached |
Wait 5-10x \(\tau_{\text{relax}}\) before recording |
\(N_1\) noise level |
< 10% of signal at each rate |
Edge fracture |
Check visual/torque for high-rate anomalies |
Inertia correction |
Apply for high rates if Re > 0.1 |
Gap uniformity |
Verify < 1% variation for parallel plates |
Temperature stability |
±0.1°C during measurement |
Sample loading |
Consistent protocol (rest time, preshear) |
Full Bayesian Workflow with Tensor Diagnostics¶
Complete workflow for publication-quality TensorialEPM analysis:
from rheojax.models import TensorialEPM
from rheojax.pipeline.bayesian import BayesianPipeline
import arviz as az
import numpy as np
# Load combined stress data
data = RheoData.from_csv(
"tensorial_data.csv",
x_col="gamma_dot",
y_col="sigma_xy",
metadata_cols=["N1", "N2"]
)
# Initialize pipeline
pipeline = BayesianPipeline()
(pipeline
.load(data)
.model(TensorialEPM, L=32, dt=0.01, yield_criterion="von_mises", w_N1=2.0)
.fit_nlsq(max_iter=300)
.fit_bayesian(
num_warmup=1000,
num_samples=2000,
num_chains=4,
seed=42
)
)
# Tensor-specific diagnostics
idata = pipeline.result.to_arviz()
# 1. Check ν posterior (should not pile against bounds)
az.plot_posterior(idata, var_names=["nu"])
assert idata.posterior["nu"].mean() < 0.495, "ν hitting bound"
# 2. Compare τ_pl_shear vs τ_pl_normal
ratio = (idata.posterior["tau_pl_normal"] /
idata.posterior["tau_pl_shear"])
print(f"τ_pl_normal/τ_pl_shear = {ratio.mean():.2f} "
f"[{np.percentile(ratio, 2.5):.2f}, {np.percentile(ratio, 97.5):.2f}]")
# 3. N₁ prediction uncertainty
gamma_dot_pred = np.logspace(-2, 1, 50)
N1_samples = pipeline.predict_posterior_samples(
gamma_dot_pred,
n_samples=100,
observable="N1"
)
N1_mean = N1_samples.mean(axis=0)
N1_hdi = az.hdi(N1_samples, hdi_prob=0.95)
# 4. Save with full metadata
pipeline.save_results(
"tensorial_fit.hdf5",
include_diagnostics=True,
include_predictions=True
)