.. _tensorial_epm: Tensorial Elasto-Plastic Model (EPM) ===================================== Quick Reference --------------- - **Use when:** Full stress tensor modeling, normal stress differences (:math:`N_1, N_2`), anisotropic yielding, flow instabilities - **Parameters:** 10 fitted (:math:`\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``, ``dt`` config - **Key equation:** :math:`\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 :math:`\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 :doc:`lattice_epm` to track the **full stress tensor**, enabling predictions of **normal stress differences** (:math:`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 -------------- .. list-table:: :widths: 15 15 70 :header-rows: 1 * - Symbol - Units - Description * - :math:`\sigma_{ij}` - Pa - Stress tensor components [:math:`\sigma_{xx}`, :math:`\sigma_{yy}`, :math:`\sigma_{xy}`] * - :math:`\sigma_{zz}` - Pa - Out-of-plane stress (from plane strain constraint) * - :math:`N_1` - Pa - First normal stress difference (:math:`\sigma_{xx} - \sigma_{yy}`) * - :math:`N_2` - Pa - Second normal stress difference (:math:`\sigma_{yy} - \sigma_{zz}`) * - :math:`\sigma_{eff}` - Pa - Effective stress (von Mises or Hill criterion) * - :math:`\dot{\gamma}^p_{ij}` - 1/s - Plastic strain rate tensor (deviatoric) * - :math:`\mathcal{G}_{ij,kl}` - — - Tensorial Eshelby propagator (4th-order) * - :math:`\nu` - — - Poisson's ratio (plane strain constraint) * - :math:`H, N` - — - Hill anisotropy parameters Physical Interpretation ----------------------- Tensorial vs Scalar EPM ~~~~~~~~~~~~~~~~~~~~~~~~ The scalar EPM models only the shear component :math:`\sigma_xy`. While this captures flow curves and avalanches, it misses: - **Normal stress differences**: :math:`N_1 = \sigma_{xx} - \sigma_{yy}` (rod climbing) and :math:`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 [:math:`\sigma_{xx}, \sigma_{yy}, \sigma_{xy}`] at each lattice site, with :math:`\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: .. math:: \boxed{ \frac{\partial \sigma_{ij}}{\partial t} = \mu \dot{\gamma} \delta_{ij} - \frac{\sigma_{ij}}{\tau_{ij}^{pl}} f(\sigma_{eff}, \sigma_c) + \sum_{kl} \mathcal{G}_{ij,kl}(\mathbf{q}) \dot{\gamma}^{pl}_{kl} } This is the tensorial extension of the scalar EPM evolution equation (see :ref:`Discrete State Variables ` in :doc:`lattice_epm`). The tensorial propagator :math:`\mathcal{G}_{ij,kl}` derives from the Eshelby inclusion solution in plane strain: .. math:: \mathcal{G}_{ij,kl}(\mathbf{q}) = C_{ijmn} \frac{q_m q_n}{|\mathbf{q}|^2} where C is the elastic stiffness tensor with shear modulus :math:`\mu` and Poisson ratio :math:`\nu`. Yield Criteria ~~~~~~~~~~~~~~ **Von Mises (Isotropic)**: .. math:: \boxed{ \sigma_{eff} = \frac{1}{\sqrt{2}} \sqrt{(\sigma_{xx} - \sigma_{yy})^2 + (\sigma_{yy} - \sigma_{zz})^2 + (\sigma_{zz} - \sigma_{xx})^2 + 6\sigma_{xy}^2} } **Hill (Anisotropic)**: .. math:: \boxed{ \sigma_{eff} = \sqrt{H (\sigma_{xx} - \sigma_{yy})^2 + 2N \sigma_{xy}^2} } 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 :ref:`epm-flow-curve` section in :doc:`lattice_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: .. math:: \dot{\gamma}^{pl}_{ij} = \frac{\sigma'_{ij}}{\tau^{pl}_{ij}} \Theta(\sigma_{eff} - \sigma_c) where :math:`\sigma'_{ij}` is the deviatoric stress. Separate :math:`\tau_{\text{pl,shear}}` and :math:`\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 (:math:`\sigma_{xy}` only) misses these phenomena because: 1. Normal components :math:`\sigma_{xx}, \sigma_{yy}` evolve independently under shear 2. Yielding in one direction affects stress redistribution in all directions 3. Anisotropic yield criteria (Hill) require full tensor Tensorial Eshelby Solution ~~~~~~~~~~~~~~~~~~~~~~~~~~~ When a site yields plastically with strain rate :math:`\dot{\gamma}^{pl}_{kl}`, the stress redistribution to site :math:`\mathbf{r}` is given by the 4th-order Eshelby tensor: .. math:: \Delta \sigma_{ij}(\mathbf{r}) = \mathcal{G}_{ij,kl}(\mathbf{r}) \dot{\gamma}^{pl}_{kl} In Fourier space (plane strain, 2D): .. math:: \tilde{\mathcal{G}}_{ij,kl}(\mathbf{q}) = C_{ijmn} \frac{q_m q_n}{|\mathbf{q}|^2} where C is the elastic stiffness tensor. For isotropic elasticity with shear modulus :math:`\mu` and Poisson ratio :math:`\nu`: .. math:: C_{ijkl} = \mu \left[ \delta_{ik}\delta_{jl} + \delta_{il}\delta_{jk} + \frac{2\nu}{1-2\nu} \delta_{ij}\delta_{kl} \right] **Key property**: The propagator couples all stress components, so a plastic event in :math:`\sigma_{xy}` affects :math:`\sigma_{xx}` and :math:`\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 :math:`\varepsilon_{zz} = 0` but stress :math:`\sigma_{zz} \neq 0`. The constraint is: .. math:: \sigma_{zz} = \nu (\sigma_{xx} + \sigma_{yy}) This coupling generates **non-zero** :math:`N_2`: .. math:: N_2 = \sigma_{yy} - \sigma_{zz} = (1 - \nu) \sigma_{yy} - \nu \sigma_{xx} even when :math:`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: .. math:: \boxed{ \sigma_{zz} = \nu (\sigma_{xx} + \sigma_{yy}) } This couples the in-plane components and leads to non-zero :math:`N_2`. Normal Stress Differences ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. math:: \boxed{ N_1 = \sigma_{xx} - \sigma_{yy} } .. math:: \boxed{ N_2 = \sigma_{yy} - \sigma_{zz} = (1 - \nu) \sigma_{yy} - \nu \sigma_{xx} } Typical experimental observations: - Polymer melts: :math:`N_1 > 0` (rod climbing), :math:`|N_2| \ll N_1` - Shear banding: Large gradients in :math:`N_1` correlate with band boundaries Fitting to Normal Stress Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The loss function for combined fitting is: .. math:: \mathcal{L} = \sum_i (\sigma_{xy,i}^{pred} - \sigma_{xy,i}^{data})^2 + w_{N1} \sum_i (N_{1,i}^{pred} - N_{1,i}^{data})^2 Set ``w_N1 > 1`` to prioritize normal stress accuracy. .. note:: Combined :math:`[\sigma_{xy}, N_1]` fitting is not yet wired through ``TensorialEPM.fit()``; the current ``fit()`` supports shear-only 1D :math:`\sigma_{xy}` targets and delegates to ``EPMBase._fit``. For now, ``w_N1`` is reserved for future use. You can still *predict* :math:`N_1` from a shear-only fit via ``model.predict(..., test_mode="flow_curve")`` which returns :math:`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 :doc:`lattice_epm` (see :ref:`fluidity_form`), applied component-wise through the Prandtl–Reuss flow direction: .. math:: \dot\varepsilon^{p}_{ij} = \frac{\sigma'_{ij}}{\sigma_\text{eff}}\, g_\text{eff}(\sigma_\text{eff}; \sigma_{c,\text{mean}}, n_{\text{fluid}})\, \cdot\frac{1}{\tau^{pl}_{ij}}\cdot \mathcal{A} where :math:`\sigma'_{ij}` is the deviatoric stress, :math:`\sigma_\text{eff}` is the yield-criterion effective stress, :math:`\tau^{pl}_{ij}` is ``tau_pl_shear`` for :math:`ij = xy` and ``tau_pl_normal`` otherwise, and :math:`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** :math:`\sqrt{3}` **factor from von Mises.** For pure shear the von Mises effective stress is :math:`\sigma_\text{eff} = \sqrt{3}\,|\sigma_{xy}|`, so yielding begins at :math:`|\sigma_{xy}| = \sigma_{c,\text{mean}}/\sqrt{3}` — **a factor of** :math:`\sqrt{3}` **below the scalar** ``sigma_c_mean``. The macroscopic yield stress you'd measure from the tensorial flow curve is .. math:: \sigma_y^{\text{tensorial}} = \frac{\sigma_{c,\text{mean}}}{\sqrt{3}} so to match a measured emulsion yield stress :math:`\sigma_y \approx 24` Pa, set the tensorial ``sigma_c_mean`` to :math:`24\sqrt{3} \approx 41.6` Pa. The scalar LatticeEPM has no such factor — there, ``sigma_c_mean`` directly equals :math:`\sigma_y`. **2. The factor-of-2 in the stress-rate convention.** The tensorial stress update follows the Budrikis–Zapperi 2013 convention :math:`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 :math:`\dot\gamma` the tensor plastic strain rate must equal :math:`\dot\gamma/2` (half the engineering shear rate). This changes the analytical flow-curve formula for the ``"overstress"`` form: .. math:: \sigma_{xy}^{\text{tensorial}}(\dot\gamma) = \frac{\sigma_{c,\text{mean}}}{\sqrt{3}} + \frac{1}{\sqrt{3}} \left(\frac{\sqrt{3}}{2}\right)^{1/n_{\text{fluid}}} \sigma_{c,\text{mean}}^{(n_{\text{fluid}}-1)/n_{\text{fluid}}} \left(\dot\gamma\,\tau_{pl,\text{shear}}\right)^{1/n_{\text{fluid}}} Special cases: - :math:`n_{\text{fluid}} = 1`: :math:`\sigma_{xy} = \sigma_{c,\text{mean}}/\sqrt{3} + \dot\gamma\,\tau_{pl,\text{shear}}/2` (Bingham with yield plateau) - :math:`n_{\text{fluid}} = 2`: :math:`\sigma_{xy} = \sigma_{c,\text{mean}}/\sqrt{3} + \sqrt{\sigma_{c,\text{mean}}\,\dot\gamma\,\tau_{pl,\text{shear}}/(2\sqrt{3})}` (HB with :math:`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: .. code-block:: python 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 :math:`R^2_{\text{lin}} > 0.993`, max relative error :math:`< 10\%` on the :math:`\phi = 0.80` emulsion). Validity and Assumptions ------------------------ **Valid for:** - Materials where **normal stresses are measurable** and significant (:math:`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**: :math:`\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 :math:`N_1` is not measured (use ``LatticeEPM`` instead) - 3D bulk flows without confinement (requires full 3D tensor implementation) - Very compressible materials (model assumes :math:`\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 ~~~~~~~~~~~~~~~~~~~~~~~~ :math:`\sigma_c` **(Yield Stress Threshold)**: Local critical stress for plastic yielding in von Mises or Hill criterion. *For graduate students*: For von Mises, :math:`\sigma_{\text{eff}} = \sqrt{\frac{1}{2}\boldsymbol{\tau}:\boldsymbol{\tau}}` must exceed :math:`\sigma_c` for plastic flow. Connects to microscopic cage breaking or bond rupture energetics. *For practitioners*: Design parameter for processing stresses. :math:`\sigma_c` sets minimum stress for continuous flow. :math:`N_1, N_2` **(Normal Stress Differences)**: First (:math:`N_1 = \sigma_{xx} - \sigma_{yy}`) and second (:math:`N_2 = \sigma_{yy} - \sigma_{zz}`) normal stress differences from tensorial stress state. *For graduate students*: :math:`N_1` arises from upper-convected Maxwell backbone (chain stretching, particle alignment). :math:`N_2` from plane strain constraint: :math:`N_2 = (1-\nu)\sigma_{yy} - \nu\sigma_{xx}`. Ratio :math:`N_1/\sigma_{xy} \sim \text{Wi}` (Weissenberg number) quantifies elasticity. *For practitioners*: Measure :math:`N_1` to predict rod climbing (Weissenberg effect), die swell, and edge fracture. :math:`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: :math:`\sigma_{\text{eff,Hill}} = \sqrt{H(\sigma_{xx}-\sigma_{yy})^2 + 2N\sigma_{xy}^2}`. :math:`H=1, N=3` recovers von Mises. Microstructurally, :math:`H` and :math:`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 ~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: Material Classification from TensorialEPM Parameters :header-rows: 1 :widths: 20 20 30 30 * - Parameter Range - Material Behavior - Typical Materials - Processing Implications * - :math:`N_1/\sigma_{xy} < 0.1` - Weakly elastic - Pastes, concentrated suspensions - Minimal normal stress effects * - :math:`N_1/\sigma_{xy} = 0.1\text{--}1` - Moderate elasticity - Emulsions, soft colloids - Rod climbing, moderate die swell * - :math:`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 * - :math:`H \neq 1` or :math:`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 :math:`\sigma_{xy}` and :math:`N_1`: - Adjust ``nu`` (Poisson ratio) to control :math:`N_1` magnitude - Adjust ``tau_pl_normal`` if :math:`N_1` relaxation differs from shear **Step 3: Test anisotropy (Hill criterion)** If isotropic fit fails (:math:`R^2 < 0.9` for :math:`N_1`): - Switch to ``yield_criterion='hill'`` - Fit ``hill_H`` and ``hill_N`` while holding other parameters fixed Parameter Bounds and Physical Constraints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :widths: 20 30 50 :header-rows: 1 * - Parameter - Typical Range - Physical Constraint * - ``nu`` - 0.40-0.49 - Avoid 0.5 (incompressible singularity); :math:`N_1` sensitive to :math:`\nu` * - ``tau_pl_shear`` - 0.01-10 s - Match shear stress relaxation timescale * - ``tau_pl_normal`` - 0.1-10× tau_pl_shear - Often similar, but can differ for anisotropic materials * - ``w_N1`` - 0.1-10 - Higher weight = prioritize :math:`N_1` fit over :math:`\sigma_{xy}` * - ``hill_H`` - 0.5-2.0 - H = 1, N = 3 recovers von Mises (isotropic) * - ``hill_N`` - 1.5-5.0 - N controls shear-normal coupling Common Fitting Issues ~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :widths: 35 65 :header-rows: 1 * - Issue - Solution * - :math:`N_1` predictions too small - Increase ``sigma_c_std`` (disorder) or reduce ``nu`` to 0.42-0.45 * - :math:`N_1` predictions too large - Increase ``nu`` toward 0.48 or reduce disorder * - Shear fit good, :math:`N_1` fit poor - Increase ``w_N1`` to 2-5; consider anisotropy (Hill) * - Convergence fails with w_N1 > 0 - Fit shear first (w_N1=0), then refine with w_N1=1 * - GPU memory overflow - Reduce ``L`` to 32 or 48; batch process long time series API Reference ------------- .. autoclass:: rheojax.models.epm.tensor.TensorialEPM :members: :undoc-members: :show-inheritance: :no-index: Parameters ---------- .. list-table:: TensorialEPM Parameters :header-rows: 1 :widths: 18 14 18 50 * - Parameter - Units - Bounds - Description * - ``mu`` - Pa - [0.1, 1e9] - Shear modulus (elastic stiffness) * - ``nu`` - dimensionless - [0.3, 0.5] - Poisson's ratio (plane strain), avoid 0.5 (incompressible singularity) * - ``tau_pl_shear`` - s - [0.01, 100.0] - Plastic relaxation time for shear components (:math:`\sigma_{xy}`) * - ``tau_pl_normal`` - s - [0.01, 100.0] - Plastic relaxation time for normal stresses (:math:`\sigma_{xx}, \sigma_{yy}`) * - ``sigma_c_mean`` - Pa - [0.1, 1e6] - Mean local yield threshold. **Important for the tensorial kernel**: the pure-shear plateau is at :math:`\sigma_{c,\text{mean}}/\sqrt{3}` (a factor of :math:`\sqrt{3}` below the scalar LatticeEPM) due to the von Mises yield criterion :math:`\sigma_\text{eff} = \sqrt{3}\,|\sigma_{xy}|`. To match a measured yield stress :math:`\sigma_y`, use :math:`\sigma_{c,\text{mean}} = \sigma_y \cdot \sqrt{3}`. * - ``sigma_c_std`` - Pa - [0.0, 100.0] - Disorder strength (std dev of thresholds) * - ``n_fluid`` - dimensionless - [0.5, 5.0] - Power-law / HB exponent of the plastic strain rate law. Implied HB shear-thinning exponent is :math:`n_{HB} = 1/n_{\text{fluid}}`. At :math:`n_{\text{fluid}}=1` the ``"overstress"`` form reduces to Bingham with an explicit yield baseline. Ignored if ``fluidity_form="linear"``. * - ``w_N1`` - dimensionless - [0.1, 10.0] - Weight for :math:`N_1` in combined fitting loss (reserved for future combined :math:`[\sigma_{xy}, N_1]` fit; currently ``TensorialEPM.fit`` supports shear-only 1D :math:`\sigma_{xy}` targets). * - ``hill_H`` - dimensionless - [0.1, 5.0] - Hill anisotropy parameter H (normal stress coupling). Works with any ``fluidity_form`` — the overstress formula uses :math:`\sigma_\text{eff}` directly regardless of how it's computed. * - ``hill_N`` - dimensionless - [0.1, 5.0] - Hill anisotropy parameter N (shear amplification). At :math:`H=0.5, N=1.5` Hill reduces exactly to von Mises for pure shear. **Configuration (not fitted)**: .. list-table:: Configuration Parameters :header-rows: 1 :widths: 20 20 60 * - Parameter - Default - Description * - ``L`` - 64 - Lattice size (L×L grid) * - ``dt`` - 0.01 - Time step for integration * - ``yield_criterion`` - ``"von_mises"`` - Yield criterion: ``"von_mises"`` (isotropic) or ``"hill"`` (anisotropic) * - ``fluidity_form`` - ``"overstress"`` - Plastic strain rate constitutive law. Same three values as scalar LatticeEPM: ``"linear"``, ``"power"``, or ``"overstress"`` (default). See :ref:`fluidity_form` in :doc:`lattice_epm` for the formulas. The tensorial variant applies them component-wise through the Prandtl–Reuss flow direction :math:`\sigma'_{ij}/\sigma_\text{eff}`. * - ``seed`` - 0 - Random seed for threshold initialization (reproducibility) Usage ----- Basic Usage ~~~~~~~~~~~ .. code-block:: python 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 :math:`N_1` Prediction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python 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 :class:`rheojax.models.epm.base.EPMBase.fit` and supports **shear-only** 1D :math:`\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: .. code-block:: python 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** :math:`\sigma_{xy}` data. Passing a 2D target with shape ``(2, n)`` for combined :math:`[\sigma_{xy}, N_1]` fitting raises ``NotImplementedError`` — that mode is reserved for a future release. For now, fit on :math:`\sigma_{xy}` and inspect :math:`N_1` from ``model.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=16`` and data subsampling during fitting; upgrade to ``L=32-64`` only for final validation and production simulations. - The scalar LatticeEPM fit parameters are **not** directly transferable to TensorialEPM — apply the :math:`\sqrt{3}` scaling on ``sigma_c_mean`` and the factor-of-2 scaling on ``tau_pl_shear`` first. See the ``Mapping scalar fit parameters to tensorial initial guesses`` code block in the *Constitutive Forms* section above. Fitting to Combined [:math:`\sigma_xy, N_1`] Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python # 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python # 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 ~~~~~~~~~~~~~ .. code-block:: python 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 --------------------------------------- .. list-table:: Model Comparison :header-rows: 1 :widths: 30 35 35 * - Use Case - LatticeEPM (Scalar) - TensorialEPM * - Flow curves (:math:`\sigma` vs :math:`\dot{\gamma}`) - ✓ Faster (3-5x) - ✓ More accurate if :math:`N_1 \neq 0` * - Yield stress determination - ✓ Sufficient - ✓ Accounts for anisotropy * - Normal stress differences - ✗ - ✓ Required * - Shear banding analysis - ~ Qualitative - ✓ Quantitative (:math:`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: 1. Normal stress data is available 2. Material shows strong anisotropy (e.g., fiber suspensions) 3. Analyzing flow instabilities (shear banding, edge fracture) 4. Modeling 3D extrusion flows Troubleshooting --------------- :math:`N_1` Predictions Are Too Small ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - **Cause**: Insufficient disorder (:math:`\sigma_{c,\text{std}}` too low) or :math:`\nu` too high (approaching incompressible limit) - **Fix**: Increase ``sigma_c_std`` to 0.2-0.5 or reduce ``nu`` to 0.40-0.45 Fitting Fails to Converge ~~~~~~~~~~~~~~~~~~~~~~~~~~ - **Cause**: Combined [:math:`\sigma_xy, N_1`] loss has competing gradients - **Fix**: Fit shear first with ``w_N1=0``, then refine with ``w_N1 > 0`` Von Mises vs Hill Give Similar Results ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - **Cause**: Hill parameters (H, N) default to isotropic values - **Fix**: Set ``hill_H`` :math:`\neq` 1 or ``hill_N`` :math:`\neq` 3 to activate anisotropy GPU Memory Overflow ~~~~~~~~~~~~~~~~~~~ - **Cause**: Large L or long simulation times - **Fix**: Reduce ``L`` to 32 or 48 for fitting, use ``L=64+`` only for production simulations References ---------- .. [1] Hébraud, P. and Lequeux, F. "Mode-coupling theory for the pasty rheology of soft glassy materials." *Physical Review Letters*, 81, 2934 (1998). https://doi.org/10.1103/PhysRevLett.81.2934 .. [2] Bocquet, L., Colin, A., and Ajdari, A. "Kinetic theory of plastic flow in soft glassy materials." *Physical Review Letters*, 103, 036001 (2009). https://doi.org/10.1103/PhysRevLett.103.036001 .. [3] Eshelby, J. D. "The determination of the elastic field of an ellipsoidal inclusion, and related problems." *Proceedings of the Royal Society A*, 241, 376-396 (1957). https://doi.org/10.1098/rspa.1957.0133 .. [4] Hill, R. "A theory of the yielding and plastic flow of anisotropic metals." *Proceedings of the Royal Society A*, 193, 281-297 (1948). https://doi.org/10.1098/rspa.1948.0045 .. [5] Bird, R. B., Armstrong, R. C., and Hassager, O. *Dynamics of Polymeric Liquids*, Vol. 1: Fluid Mechanics, 2nd Edition. Wiley (1987). ISBN: 978-0471802457 .. [6] Picard, G., Ajdari, A., Lequeux, F., and Bocquet, L. "Elastic consequences of a single plastic event: A step towards the microscopic modeling of the flow of yield stress fluids." *European Physical Journal E*, 15, 371-381 (2004). https://doi.org/10.1140/epje/i2004-10054-8 .. [7] Nicolas, A., Ferrero, E. E., Martens, K., and Barrat, J.-L. "Deformation and flow of amorphous solids: Insights from elastoplastic models." *Reviews of Modern Physics*, 90, 045006 (2018). https://doi.org/10.1103/RevModPhys.90.045006 .. [8] Larson, R. G. "Constitutive equations for polymer melts and solutions." *Butterworths Series in Chemical Engineering*, Boston (1988). ISBN: 978-0409901191 .. [9] Coussot, P. "Yield stress fluid flows: A review of experimental data." *Journal of Non-Newtonian Fluid Mechanics*, 211, 31-49 (2014). https://doi.org/10.1016/j.jnnfm.2014.05.006 .. [10] Saramito, P. "A new elastoviscoplastic model based on the Herschel-Bulkley viscoplastic model." *Journal of Non-Newtonian Fluid Mechanics*, 158, 154-161 (2009). https://doi.org/10.1016/j.jnnfm.2008.12.001 See Also -------- - :doc:`index` — EPM family overview and comparison - :doc:`lattice_epm` — Scalar EPM for faster fitting when :math:`N_1` data unavailable - :doc:`/models/sgr/sgr_conventional` — Mean-field SGR (complementary theory) - :doc:`/models/hl/hebraud_lequeux` — Mean-field HL (EPM limiting case) - :py:func:`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 -------------------- .. code-block:: python 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: .. list-table:: Default Priors for TensorialEPM Parameters :widths: 20 35 45 :header-rows: 1 * - Parameter - Prior Distribution - Rationale * - :math:`\mu` (shear modulus) - LogNormal(log(10), 1.5) - Positive, spans 0.1-1000 Pa * - :math:`\nu` (Poisson ratio) - Beta(8, 2) scaled to [0.3, 0.5] - Constrained near incompressible * - :math:`\sigma_{c,\text{mean}}` - HalfNormal(10) - Positive, order of yield stress * - :math:`\sigma_{c,\text{std}}` - HalfNormal(2) - Positive, smaller than mean * - :math:`\tau_{\text{pl,shear}}` - LogNormal(log(1), 1) - Positive, log-uniform over decades * - :math:`\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:** .. code-block:: python 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: .. list-table:: Diagnostic Guidelines for TensorialEPM :widths: 25 25 50 :header-rows: 1 * - 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:** - :math:`\nu` **↔** :math:`\sigma_{c,\text{std}}`: Higher Poisson ratio compensates for lower disorder - :math:`\tau_{\text{pl,shear}}` **↔** :math:`\tau_{\text{pl,normal}}`: Often correlated; consider fixing ratio - **hill_H ↔ hill_N**: Strong correlation in anisotropic fits .. code-block:: python 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 [:math:`\sigma_xy, N_1`] Fitting Strategy ------------------------------------ Fitting to both shear and normal stress data requires careful balancing: **Strategy 1: Sequential fitting** 1. Fit :math:`\sigma_{xy}` only (w_N1 = 0) to get :math:`\mu, \sigma_c, \tau_{\text{pl}}` 2. Fix shear parameters, fit :math:`\nu, \tau_{\text{pl,normal}}` from :math:`N_1` 3. Joint refinement with w_N1 = 1 .. code-block:: python # 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** .. code-block:: python # 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 - :math:`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: :math:`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 :math:`N_1` depends on aspect ratio and concentration Rate-Dependence of Normal Stresses ---------------------------------- The ratio :math:`N_1/\sigma_{xy}` typically shows characteristic rate-dependence: **Low shear rates (linear regime):** .. math:: N_1 \sim \gamma \cdot \sigma_{xy} \sim \dot{\gamma}^2 In this regime, :math:`N_1/\sigma_{xy} \sim \text{Wi}` (Weissenberg number). **Intermediate rates (non-linear):** .. math:: N_1 \sim \dot{\gamma}^\alpha \quad \text{with } 0.5 < \alpha < 2 Power-law scaling with exponent depending on microstructure. **High shear rates (shear thinning):** .. math:: N_1 / \sigma_{xy} \rightarrow \text{const} Plateau ratio indicates fully aligned microstructure. **Diagnostic:** :math:`N_1/\sigma_{xy}` **ratio** .. code-block:: python 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: .. math:: \sigma_{eff}^2 = F(\sigma_{yy} - \sigma_{zz})^2 + G(\sigma_{zz} - \sigma_{xx})^2 + H(\sigma_{xx} - \sigma_{yy})^2 + 2L\sigma_{yz}^2 + 2M\sigma_{zx}^2 + 2N\sigma_{xy}^2 In plane stress (:math:`\sigma_{zz} = \sigma_{xz} = \sigma_{yz} = 0`): .. math:: \sigma_{eff} = \sqrt{H (\sigma_{xx} - \sigma_{yy})^2 + 2N \sigma_{xy}^2} **Parameter interpretation:** - **H**: Resistance to normal stress difference (:math:`\sigma_{xx} - \sigma_{yy}`) - **N**: Resistance to shear stress :math:`\sigma_{xy}` - **H = 1, N = 3**: Recovers von Mises (isotropic) - :math:`H > 1`: Stronger in normal direction (resists :math:`N_1` generation) - :math:`N > 3`: Stronger in shear (higher yield stress for same :math:`\sigma_{xy}`) Connection to Fiber Orientation Tensor -------------------------------------- For fiber suspensions, Hill parameters relate to the orientation tensor **a**: .. math:: H \sim 1 + c_H \langle p_x^2 - p_y^2 \rangle N \sim 3 + c_N \langle p_x^2 p_y^2 \rangle 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 ----------------------------- .. code-block:: python 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 :math:`N_1` measurement (uniform shear rate) - Edge fracture limits high rates (:math:`N_1 \sim \sigma_{xy}` at critical Wi) - Requires thrust bearing calibration **Parallel plates:** - :math:`N_1` requires correction: :math:`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:** - :math:`N_1` from exit pressure (Bagley plot) - High shear rates accessible - End corrections essential .. list-table:: Geometry Comparison for Normal Stress :widths: 25 25 25 25 :header-rows: 1 * - Geometry - :math:`N_1` Accuracy - Rate Range - Limitations * - Cone-plate - Excellent - 10\ :math:`^{-3 - 10^2}` 1/s - Edge fracture at high Wi * - Parallel plate - Good (with correction) - 10\ :math:`^{-2 - 10^3}` 1/s - Gap-dependent correction * - Capillary - Moderate - :math:`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 :math:`\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 :math:`N_1` noise: high variance indicates instability Data Quality Checklist ---------------------- Before fitting TensorialEPM, verify data quality: .. list-table:: Data Quality Checklist :widths: 35 65 :header-rows: 1 * - Check - Requirement * - Steady state reached - Wait 5-10x :math:`\tau_{\text{relax}}` before recording * - :math:`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: .. code-block:: python 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 )