.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples/continuum_mechanics/tensor.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_examples_continuum_mechanics_tensor.py: Tensor2 and Tensor4 Examples ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``Tensor2`` and ``Tensor4`` classes wrap simcoon's C++ tensor objects. They carry a **type tag** (stress/strain for ``Tensor2``, stiffness/compliance for ``Tensor4``), which automatically selects the correct Voigt convention, rotation rule, and push-forward/pull-back formula. A single class handles **both** individual tensors and **batches** of N tensors (like ``scipy.spatial.transform.Rotation``). Shape determines which mode: * ``(6,)`` or ``(3,3)`` → single ``Tensor2`` * ``(N,6)`` or ``(N,3,3)`` → batch of N ``Tensor2`` * ``(6,6)`` → single ``Tensor4`` * ``(N,6,6)`` → batch of N ``Tensor4`` Batch operations loop in C++ over the same functions used by single tensors, so results are always bit-identical. .. GENERATED FROM PYTHON SOURCE LINES 21-25 .. code-block:: Python import numpy as np import simcoon as sim .. GENERATED FROM PYTHON SOURCE LINES 26-30 1. Building typed tensors ------------------------- The factory methods ``stress()``, ``strain()``, ``stiffness()``, etc. embed the Voigt convention into the object. .. GENERATED FROM PYTHON SOURCE LINES 30-40 .. code-block:: Python sigma = sim.Tensor2.stress(np.array([100, 200, 300, 30, 20, 40], dtype=float)) print("sigma.vtype:", sigma.vtype) print("sigma.voigt:", sigma.voigt) print("sigma.mat:\n", sigma.mat) eps = sim.Tensor2.strain(np.array([0.01, 0.02, 0.03, 0.01, 0.006, 0.008])) print("\neps.vtype:", eps.vtype) print("eps.voigt:", eps.voigt) .. rst-class:: sphx-glr-script-out .. code-block:: none sigma.vtype: stress sigma.voigt: [100. 200. 300. 30. 20. 40.] sigma.mat: [[100. 30. 20.] [ 30. 200. 40.] [ 20. 40. 300.]] eps.vtype: strain eps.voigt: [0.01 0.02 0.03 0.01 0.006 0.008] .. GENERATED FROM PYTHON SOURCE LINES 41-45 2. Stiffness and compliance ---------------------------- ``Tensor4`` wraps a 6x6 Voigt matrix with its type. Inversion automatically flips the type (stiffness <-> compliance). .. GENERATED FROM PYTHON SOURCE LINES 45-55 .. code-block:: Python L = sim.Tensor4.stiffness(sim.L_iso([70000, 0.3], "Enu")) print("L.type:", L.type) M = L.inverse() print("M.type:", M.type) # compliance L_back = M.inverse() print("Roundtrip match:", np.allclose(L.mat, L_back.mat)) .. rst-class:: sphx-glr-script-out .. code-block:: none L.type: stiffness M.type: compliance Roundtrip match: True .. GENERATED FROM PYTHON SOURCE LINES 56-60 3. Contraction: sigma = L : epsilon ------------------------------------ The ``@`` operator contracts a ``Tensor4`` with a ``Tensor2``. The output type is inferred: stiffness @ strain -> stress. .. GENERATED FROM PYTHON SOURCE LINES 60-65 .. code-block:: Python sigma = L @ eps print("sigma.vtype:", sigma.vtype) # stress print("sigma.voigt:", np.array_str(sigma.voigt, precision=4)) .. rst-class:: sphx-glr-script-out .. code-block:: none sigma.vtype: stress sigma.voigt: [2961.5385 3500. 4038.4615 269.2308 161.5385 215.3846] .. GENERATED FROM PYTHON SOURCE LINES 66-69 4. Scalar invariants --------------------- ``trace()`` and ``mises()`` are type-aware. .. GENERATED FROM PYTHON SOURCE LINES 69-73 .. code-block:: Python print("Trace:", sigma.trace()) print("Von Mises:", sigma.mises()) .. rst-class:: sphx-glr-script-out .. code-block:: none Trace: 10499.999999999998 Von Mises: 1142.2494157628842 .. GENERATED FROM PYTHON SOURCE LINES 74-79 5. Rotation via simcoon.Rotation --------------------------------- Rotation dispatches on the type tag: stress uses QS, strain uses QE, stiffness uses QS on both sides, compliance uses QE, etc. The rotation roundtrip is exact. .. GENERATED FROM PYTHON SOURCE LINES 79-90 .. code-block:: Python rot = sim.Rotation.from_euler("ZXZ", [0.3, 0.5, 0.7]) sigma_rot = sigma.rotate(rot) sigma_back = sigma_rot.rotate(rot.inv()) print("Rotation roundtrip error:", np.max(np.abs(sigma.mat - sigma_back.mat))) # Isotropic stiffness is invariant under any rotation L_rot = L.rotate(rot) print("Isotropic L unchanged:", np.allclose(L.mat, L_rot.mat, atol=1e-8)) .. rst-class:: sphx-glr-script-out .. code-block:: none Rotation roundtrip error: 1.3642420526593924e-12 Isotropic L unchanged: True .. GENERATED FROM PYTHON SOURCE LINES 91-96 6. Push-forward and pull-back ------------------------------- Stress (contravariant): ``F * sigma * F^T``. Strain (covariant): ``F^{-T} * eps * F^{-1}``. The type tag selects the correct formula automatically. .. GENERATED FROM PYTHON SOURCE LINES 96-105 .. code-block:: Python F = np.array([[1.1, 0.1, 0.05], [0.02, 0.95, 0.03], [0.01, 0.04, 1.05]]) eps_push = eps.push_forward(F) eps_back = eps_push.pull_back(F) print("Push/pull roundtrip error:", np.max(np.abs(eps.mat - eps_back.mat))) .. rst-class:: sphx-glr-script-out .. code-block:: none Push/pull roundtrip error: 6.938893903907228e-18 .. GENERATED FROM PYTHON SOURCE LINES 106-110 7. Material-frame equivalence -------------------------------- ``L : eps`` in the material frame equals ``(rotate L) : (rotate eps)`` in the global frame. This is the fundamental rotation identity. .. GENERATED FROM PYTHON SOURCE LINES 110-120 .. code-block:: Python L_cubic = sim.Tensor4.stiffness(sim.L_cubic([200000, 0.3, 80000], "EnuG")) eps_test = sim.Tensor2.strain(np.array([0.01, -0.003, -0.003, 0.005, 0.002, 0.001])) sigma_mat = L_cubic @ eps_test sigma_global = L_cubic.rotate(rot) @ eps_test.rotate(rot) sigma_check = sigma_mat.rotate(rot) print("Frame equivalence error:", np.max(np.abs(sigma_global.mat - sigma_check.mat))) .. rst-class:: sphx-glr-script-out .. code-block:: none Frame equivalence error: 6.821210263296962e-13 .. GENERATED FROM PYTHON SOURCE LINES 121-124 8. Arithmetic and dyadic product ---------------------------------- Tensors support ``+``, ``-``, scalar ``*``, and the dyadic product. .. GENERATED FROM PYTHON SOURCE LINES 124-136 .. code-block:: Python t1 = sim.Tensor2.stress(np.array([100, 0, 0, 0, 0, 0], dtype=float)) t2 = sim.Tensor2.stress(np.array([0, 100, 0, 0, 0, 0], dtype=float)) t3 = t1 + t2 print("t1 + t2:", t3.voigt) print("2 * t1:", (2 * t1).voigt) C = sim.dyadic(t1, t2) print("dyadic type:", C.type) print("C[0,1] =", C.mat[0, 1]) .. rst-class:: sphx-glr-script-out .. code-block:: none t1 + t2: [100. 100. 0. 0. 0. 0.] 2 * t1: [200. 0. 0. 0. 0. 0.] dyadic type: stiffness C[0,1] = 10000.0 .. GENERATED FROM PYTHON SOURCE LINES 137-140 9. Building isotropic stiffness from projectors -------------------------------------------------- Using the volumetric and deviatoric identity tensors. .. GENERATED FROM PYTHON SOURCE LINES 140-149 .. code-block:: Python E, nu = 70000.0, 0.3 K = E / (3.0 * (1.0 - 2.0 * nu)) mu = E / (2.0 * (1.0 + nu)) L_proj = 3.0 * K * sim.Tensor4.volumetric() + 2.0 * mu * sim.Tensor4.deviatoric() print("L from projectors matches L_iso:", np.allclose(L_proj.mat, sim.L_iso([E, nu], "Enu"), atol=1e-8)) .. rst-class:: sphx-glr-script-out .. code-block:: none L from projectors matches L_iso: True .. GENERATED FROM PYTHON SOURCE LINES 150-153 ===================================================================== Batch operations — same class, automatic from shape ===================================================================== .. GENERATED FROM PYTHON SOURCE LINES 155-159 10. Creating a batch from an (N, 6) array ------------------------------------------- Passing a 2D array to ``stress()`` creates a batch. ``.single`` tells you which mode you are in. .. GENERATED FROM PYTHON SOURCE LINES 159-168 .. code-block:: Python N = 100 rng = np.random.default_rng(42) eps_batch = sim.Tensor2.strain(rng.standard_normal((N, 6)) * 0.01) print("eps_batch.single:", eps_batch.single) print("len(eps_batch):", len(eps_batch)) print("eps_batch.voigt.shape:", eps_batch.voigt.shape) # (100, 6) .. rst-class:: sphx-glr-script-out .. code-block:: none eps_batch.single: False len(eps_batch): 100 eps_batch.voigt.shape: (100, 6) .. GENERATED FROM PYTHON SOURCE LINES 169-172 11. Indexing and iteration --------------------------- Batch tensors support ``len``, ``[]``, ``for t in batch``, ``reversed``. .. GENERATED FROM PYTHON SOURCE LINES 172-183 .. code-block:: Python first = eps_batch[0] print("first.single:", first.single) print("first.voigt:", np.array_str(first.voigt, precision=6)) sub = eps_batch[10:15] print("sub-batch length:", len(sub)) for i, t in enumerate(eps_batch[:3]): print(f" [{i}] mises = {t.mises():.6f}") .. rst-class:: sphx-glr-script-out .. code-block:: none first.single: True first.voigt: [ 0.003047 -0.0104 0.007505 0.009406 -0.01951 -0.013022] sub-batch length: 5 [0] mises = 0.018131 [1] mises = 0.008777 [2] mises = 0.009903 .. GENERATED FROM PYTHON SOURCE LINES 184-187 12. Batch contraction: sigma = L : eps for 100 Gauss points -------------------------------------------------------------- A single ``Tensor4`` is broadcast across the batch of ``Tensor2``. .. GENERATED FROM PYTHON SOURCE LINES 187-198 .. code-block:: Python L = sim.Tensor4.stiffness(sim.L_iso([70000, 0.3], "Enu")) sigma_batch = L @ eps_batch print("sigma_batch.vtype:", sigma_batch.vtype) # stress print("sigma_batch shape:", sigma_batch.voigt.shape) # (100, 6) # Or with a batch of stiffness tensors L_batch = sim.Tensor4.from_tensor(L, N) sigma_batch2 = L_batch @ eps_batch print("Batch L @ batch eps match:", np.allclose(sigma_batch.voigt, sigma_batch2.voigt)) .. rst-class:: sphx-glr-script-out .. code-block:: none sigma_batch.vtype: stress sigma_batch shape: (100, 6) Batch L @ batch eps match: True .. GENERATED FROM PYTHON SOURCE LINES 199-202 13. Batch von Mises and trace -------------------------------- Scalar invariants return ``(N,)`` arrays for batches. .. GENERATED FROM PYTHON SOURCE LINES 202-208 .. code-block:: Python vm = sigma_batch.mises() tr = sigma_batch.trace() print("Von Mises: min={:.2f}, max={:.2f}".format(vm.min(), vm.max())) print("Trace: min={:.2f}, max={:.2f}".format(tr.min(), tr.max())) .. rst-class:: sphx-glr-script-out .. code-block:: none Von Mises: min=430.20, max=2546.23 Trace: min=-8364.89, max=9318.75 .. GENERATED FROM PYTHON SOURCE LINES 209-213 14. Batch rotation with scipy -------------------------------- Rotate N tensors by N different rotations (one per Gauss point). The C++ loop calls the exact same rotation code as single tensors. .. GENERATED FROM PYTHON SOURCE LINES 213-227 .. code-block:: Python from scipy.spatial.transform import Rotation as ScipyRotation rotations = ScipyRotation.random(N, random_state=rng) sigma_rotated = sigma_batch.rotate(rotations) sigma_restored = sigma_rotated.rotate(rotations.inv()) print("Rotation roundtrip error:", np.max(np.abs(sigma_batch.voigt - sigma_restored.voigt))) # Single rotation broadcast to all tensors single_rot = ScipyRotation.from_euler("z", 45, degrees=True) sigma_rot_bc = sigma_batch.rotate(single_rot) print("Broadcast rotation shape:", sigma_rot_bc.voigt.shape) .. rst-class:: sphx-glr-script-out .. code-block:: none Rotation roundtrip error: 2.7284841053187847e-12 Broadcast rotation shape: (100, 6) .. GENERATED FROM PYTHON SOURCE LINES 228-231 15. Batch push-forward / pull-back ------------------------------------- Works with a single F (broadcast) or (N, 3, 3) per-point. .. GENERATED FROM PYTHON SOURCE LINES 231-243 .. code-block:: Python F_single = np.eye(3) + 0.05 * rng.standard_normal((3, 3)) pushed = eps_batch.push_forward(F_single) pulled = pushed.pull_back(F_single) print("Push/pull roundtrip error:", np.max(np.abs(eps_batch.voigt - pulled.voigt))) # Per-point deformation gradients F_batch = np.eye(3) + 0.05 * rng.standard_normal((N, 3, 3)) pushed_pp = eps_batch.push_forward(F_batch) pulled_pp = pushed_pp.pull_back(F_batch) print("Per-point push/pull error:", np.max(np.abs(eps_batch.voigt - pulled_pp.voigt))) .. rst-class:: sphx-glr-script-out .. code-block:: none Push/pull roundtrip error: 1.734723475976807e-17 Per-point push/pull error: 1.3877787807814457e-17 .. GENERATED FROM PYTHON SOURCE LINES 244-246 16. Batch Tensor4 inverse and rotation ----------------------------------------- .. GENERATED FROM PYTHON SOURCE LINES 246-258 .. code-block:: Python L_batch = sim.Tensor4.from_tensor( sim.Tensor4.stiffness(sim.L_iso([70000, 0.3], "Enu")), N ) M_batch = L_batch.inverse() print("M_batch.type:", M_batch.type) # compliance print("L -> M -> L roundtrip:", np.allclose(L_batch.voigt, M_batch.inverse().voigt, atol=1e-8)) L_rotated = L_batch.rotate(rotations) L_restored = L_rotated.rotate(rotations.inv()) print("L rotation roundtrip error:", np.max(np.abs(L_batch.voigt - L_restored.voigt))) .. rst-class:: sphx-glr-script-out .. code-block:: none M_batch.type: compliance L -> M -> L roundtrip: True L rotation roundtrip error: 4.802132025361061e-10 .. GENERATED FROM PYTHON SOURCE LINES 259-261 17. Concatenation and construction from lists ------------------------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 261-271 .. code-block:: Python part1 = sim.Tensor2.stress(rng.standard_normal((50, 6))) part2 = sim.Tensor2.stress(rng.standard_normal((50, 6))) combined = sim.Tensor2.concatenate([part1, part2]) print("Combined length:", len(combined)) singles = [sim.Tensor2.stress(np.array([float(i)] * 6)) for i in range(5)] batch_from_list = sim.Tensor2(singles) print("From list:", len(batch_from_list), "tensors") .. rst-class:: sphx-glr-script-out .. code-block:: none Combined length: 100 From list: 5 tensors .. GENERATED FROM PYTHON SOURCE LINES 272-275 18. numpy interop ------------------- ``np.asarray(batch)`` returns the Voigt data. .. GENERATED FROM PYTHON SOURCE LINES 275-280 .. code-block:: Python arr = np.asarray(sigma_batch) print("np.asarray shape:", arr.shape) # (100, 6) print("dtype:", arr.dtype) .. rst-class:: sphx-glr-script-out .. code-block:: none np.asarray shape: (100, 6) dtype: float64 .. GENERATED FROM PYTHON SOURCE LINES 281-284 19. Arithmetic on batches ---------------------------- Element-wise ``+``, ``-``, scalar ``*``, per-point ``*``. .. GENERATED FROM PYTHON SOURCE LINES 284-293 .. code-block:: Python double = sigma_batch * 2.0 diff = double - sigma_batch print("2*sigma - sigma == sigma:", np.allclose(diff.voigt, sigma_batch.voigt)) factors = rng.uniform(0.5, 1.5, N) scaled = sigma_batch * factors print("Per-point scaling shape:", scaled.voigt.shape) .. rst-class:: sphx-glr-script-out .. code-block:: none 2*sigma - sigma == sigma: True Per-point scaling shape: (100, 6) .. GENERATED FROM PYTHON SOURCE LINES 294-297 20. Material-frame equivalence — batch version -------------------------------------------------- Same identity as the single case, but across 100 orientations at once. .. GENERATED FROM PYTHON SOURCE LINES 297-310 .. code-block:: Python L_mat = sim.Tensor4.stiffness(sim.L_cubic([200000, 0.3, 80000], "EnuG")) L_batch = sim.Tensor4.from_tensor(L_mat, N) eps_batch = sim.Tensor2.strain(rng.standard_normal((N, 6)) * 0.01) sigma_mat = L_batch @ eps_batch # material frame L_global = L_batch.rotate(rotations) # rotate L eps_global = eps_batch.rotate(rotations) # rotate eps sigma_global = L_global @ eps_global # global frame sigma_check = sigma_mat.rotate(rotations) # rotate result print("Batch frame equivalence error:", np.max(np.abs(sigma_global.voigt - sigma_check.voigt))) .. rst-class:: sphx-glr-script-out .. code-block:: none Batch frame equivalence error: 8.185452315956354e-12 .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.012 seconds) .. _sphx_glr_download_examples_continuum_mechanics_tensor.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: tensor.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: tensor.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: tensor.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_