"""The 5-material zoo from W4-5 and its analytic admissible-set rules. Single source of truth for material definitions and the analytic local projection they imply (mirrors PDSolver3D._project_local); W7's fluid token extends this list with "water" at index 5. """ import numpy as np MATERIALS = { "steel": dict(stiffness=5e6, strain_limit=0.012), "rubber": dict(stiffness=1e5, strain_limit=0.35, incompressible=True), "foam": dict(stiffness=2e5, # volumetric free, shear-limited strain_limit=[0.30, 0.30, 0.30, 0.06, 0.06, 0.06]), "composite": dict(stiffness=8e5, # compliant along x, stiff crosswise strain_limit=[0.20, 0.02, 0.02, 0.12, 0.02, 0.12]), "gel": dict(stiffness=1.2e5, strain_limit=0.15), } def apply_limit(e, mat): """The material's admissible-set projection on (N,6) strain.""" lim = mat.get("strain_limit") if np.ndim(lim) > 0: e = np.clip(e, -np.asarray(lim), np.asarray(lim)) elif lim is not None: norm = np.linalg.norm(e, axis=1) e = e * np.minimum(1.0, lim / np.maximum(norm, 1e-12))[:, None] if mat.get("incompressible"): e = e.copy() e[:, :3] -= e[:, :3].mean(axis=1, keepdims=True) return e