Typed Tensors (Tensor2 / Tensor4)

simcoon.Tensor2 and simcoon.Tensor4 are unified typed wrappers for 2nd- and 4th-order tensors. A single object transparently represents either one tensor or a batch (scipy Rotation style): shape (6,) / (6,6) is a single tensor, (N,6) / (N,6,6) a batch. Tensor4 stores its data internally in the Kelvin-Mandel convention, so the double contraction, inverse and composition of 4th-order tensors become ordinary 6x6 linear algebra; the engineering Voigt form is recovered on demand via .mat / .voigt.

Objects are built through typed factories, e.g. Tensor2.stress(v), Tensor2.strain(v), Tensor4.stiffness(m), Tensor4.compliance(m), Tensor4.from_voigt(v, type_str).

Batch operations

Batch contract, rotate, push_forward, pull_back and inverse dispatch to vectorised kernels over the (N, ...) data.

Tip

Shared-tangent contraction is ~20x faster — pass a single Tensor4.

When the same 4th-order tensor is contracted against many 2nd-order tensors (a shared elastic tangent applied at every integration point), pass a single Tensor4 and contract it with a batch Tensor2:

L   = Tensor4.stiffness(L6x6)     # single (6,6)
eps = Tensor2.strain(eps_Nx6)     # batch  (N,6)
sig = L.contract(eps)             # one BLAS GEMM  ->  ~20x

This collapses to a single L @ X matrix-matrix product (BLAS dgemm): measured ~3.7 ns/point vs ~74 ns/point for the per-point path (N = 1e5). The C++ batch_contract implements the same fast path when the tensor4 cube has a single slice (N4 == 1).

Anti-pattern: materialising the shared tangent as a tiled (N,6,6) batch of identical slices — that forces N independent 6x6 matrix-vector products and the GEMM speed-up is lost. Tile the tangent only when it genuinely differs per point (a distinct consistent tangent, e.g. plasticity); there a GEMM collapse is not possible and per-point evaluation is correct.

Note

A dedicated symmetric symtensor2 (Kelvin-Mandel 6-vector storage) was benchmarked as a way to speed the general contraction path and rejected: it yields only ~1.1x on a single contraction and ~1.07x on realistic distinct-tangent batch work, and Armadillo fixed-size containers negate the 6-vs-9-double storage saving. The real ~20x lever is the shared-tangent GEMM path above, which needs no new type; the C++ tensor2 stays the general 3x3 type (the only one able to hold a non-symmetric tensor such as F, L, R). The Python Tensor2 stores the 6 Voigt components for the symmetric types ("stress", "strain", "symmetric") and the 9 components of any 3x3 for the type "none" (no Voigt convention); a non-symmetric matrix given to a symmetric type is refused rather than silently symmetrised.

Basis: the reference system of the components

Objectivity implies that the physical laws are independent of the choice of reference frame. The components of a tensor only mean something together with the basis they are written in. By default that basis is the fixed orthonormal laboratory basis \(\mathbf{e}_i\): t.basis is None and nothing is stored. A simcoon.Basis makes another choice explicit. It holds the basis vectors \(\mathbf{g}_i\) through the matrix \(\mathbf{A}\) whose columns are their laboratory frame components, and comes in two kinds:

  • orthonormal, built from a rotation, \(\mathbf{g}_i = \mathbf{R}\,\mathbf{e}_i\) (a material frame, a corotational frame). Its metric is the identity, so every formula is the one of the lab.

  • natural, any three independent vectors, typically the convected basis \(\mathbf{g}_i = \mathbf{F}\,\mathbf{G}_i\) (Basis.from_F). Its metric \(g_{ij} = \mathbf{g}_i \cdot \mathbf{g}_j\), i.e. \(\mathbf{g} = \mathbf{A}^T \mathbf{A}\), enters the invariants. ‘Natural’ definition come from the convected basis of a material point.

The type tag gives the variance of the components: a stress is contravariant, \(\boldsymbol{\sigma} = \sigma^{ij}\,\mathbf{g}_i \otimes \mathbf{g}_j\), a strain covariant, \(\boldsymbol{\varepsilon} = \varepsilon_{ij}\,\mathbf{g}^i \otimes \mathbf{g}^j\); a stiffness is contravariant on its four indices, a compliance covariant, the concentration tensors mixed. The lab components follow:

\[\boldsymbol{\sigma}_{lab} = \mathbf{A}\,\hat{\boldsymbol{\sigma}}\,\mathbf{A}^T, \qquad \boldsymbol{\varepsilon}_{lab} = \mathbf{A}^{-T}\,\hat{\boldsymbol{\varepsilon}}\,\mathbf{A}^{-1}.\]
Default variance of each type (t.variance)

Type

Components

Transport by \(\mathbf{F}\) (push_forward)

Tensor2 "stress"

\(\sigma^{ij}\) (contravariant)

\(\mathbf{F}\,\boldsymbol{\sigma}\,\mathbf{F}^T\), weight \(1/J\) with metric=True

Tensor2 "strain"

\(\varepsilon_{ij}\) (covariant)

\(\mathbf{F}^{-T}\,\boldsymbol{\varepsilon}\,\mathbf{F}^{-1}\)

Tensor2 "symmetric", "none" (any 3x3, no Voigt convention)

lab or orthonormal-frame components only

none: no variance, hence no natural basis and no transport

Tensor4 "stiffness", "generic"

\(C^{ijkl}\)

\(\mathbf{F}\) on the four indices, weight \(1/J\)

Tensor4 "compliance"

\(M_{ijkl}\)

\(\mathbf{F}^{-T}\) on the four indices, weight \(J\)

Tensor4 concentrations

mixed, \(A_{ij}{}^{kl}\) / \(B^{ij}{}_{kl}\)

pair-wise: \(\mathbf{F}^{-T}\) on the covariant pair, \(\mathbf{F}\) on the contravariant one

The variance is a tag of the components, t.variance, carried next to the type and the basis: "contravariant" or "covariant" for a Tensor2, a pair (output, input) for a Tensor4. The table gives its default; it is read, not the type, by the transports, by to_basis and by the metric invariants. Two things the default is not:

  • It is not the only possible representation. Variance is a choice made for the components, not a property of the tensor: with the metric any tensor can be given contravariant or covariant components (det and eigvals use the mixed ones \(T^i{}_j = T^{ik} g_{kj}\) internally). The default is the one under which the conjugate pairs \(\mathbf{S} : \mathbf{E}\) and \(\boldsymbol{\tau} : \mathbf{e}\) contract without a metric and the convected components stay constant under transport. t.to_variance(v) gives the same tensor with components of the other variance: a retag in the lab or an orthonormal basis (the numbers do not change), a contraction with the metric in a natural basis, \(\sigma_{ij} = g_{ik}\,\sigma^{kl}\,g_{lj}\) (or its inverse), pair by pair for a Tensor4. The Voigt shear factors stay those of the type. Mixed components of a single pair are not representable in 6 components (they are not symmetric), hence a tag per pair. The variance is part of what a transport means: a covariantly tagged stiffness pushes forward with \(\mathbf{F}^{-T}\) on its four indices, which is another tensor than the push-forward of the contravariant one. A "symmetric" Tensor2 has no default variance: to_variance declares it before any transport or natural basis; type "none" has none.

  • It is not a statement about every strain measure. The covariant transport is that of the Green-Lagrange / Almansi pair. A logarithmic strain is a tensor function of a stretch, not the transport of anything: push_forward on a strain-typed \(\ln \mathbf{V}\) is accepted by the type tag and has no meaning.

Two-point tensors have no basis

The deformation gradient \(\mathbf{F} = F^i{}_J\,\mathbf{g}_i \otimes \mathbf{G}^J\) has one index in each configuration, so there is no single basis to attach it to; in the convected pair \((\mathbf{G}_i, \mathbf{g}_i = \mathbf{F}\,\mathbf{G}_i)\) its mixed components are \(\delta^i_J\): it is the relation between the two bases. This is why F is always a plain array of lab components in the API, consumed by Basis.from_F and by the transports, never wrapped. The same holds for the rotation \(\mathbf{R}\) of the polar decomposition, the rotation increment \(\Delta\mathbf{R}\) and the first Piola-Kirchhoff stress. They can be held in a Tensor2 of type "none" (no Voigt convention: 9 components, lab-lab). Such a tensor has no variance, hence no transport and no natural basis (re-expressing its components would need the variance of each leg); it does accept an orthonormal Basis, both legs being read in that frame, since \(\mathbf{Q}^T \mathbf{X}\,\mathbf{Q}\) is the same for every variance. It offers the trace, determinant, norm and eigenvalues, but no Voigt vector.

Three operations, three meanings:

Call

Meaning

t.to_basis(b)

The same tensor, its components re-expressed in b (None = lab).

t.with_basis(b)

The same components in another basis: a transport, hence another tensor. No arithmetic, no copy.

t.rotate(R, active=False)

The same tensor in the frame turned by R; the result carries that basis.

Tensors written in different bases cannot be combined: +, -, @, % and the dyadic products raise ValueError("Mixed basis"). A name given to a basis is a label for repr and error messages; two bases are the same when their vectors are.

import numpy as np
import simcoon as sim

material = sim.Basis(rotation=sim.Rotation.from_euler("z", 30, degrees=True),
                     name="material")
sigma = sim.Tensor2.stress(np.array([100., 0., 0., 0., 0., 0.]))
sigma_m = sigma.to_basis(material)     # material-frame components
sigma + sigma_m                        # ValueError: Mixed basis
sigma_m.to_basis(None)                 # lab components again

One basis can serve a whole batch (it is stored once and shared by reference by every tensor derived from it), or a batch of N bases can give one per tensor.

A basis also knows its reciprocal vectors \(\mathbf{g}^i\) (reciprocal, the columns of \(\mathbf{A}^{-T}\), on which covariant components live), its right stretch \(\mathbf{U} = \sqrt{\mathbf{g}}\) (stretch) and the orthonormal basis closest to it, the rotation of the polar decomposition \(\mathbf{A} = \mathbf{R}\,\mathbf{U}\) (polar). For a convected basis these are the right stretch and the rotation of \(\mathbf{F}\); polar is the rotated orthonormal frame a convected tensor is often read in.

Push-forward and pull-back carry the basis along

A push-forward is a transport, not a change of basis: the result is another tensor, living in the current configuration. But in the convected basis its components are those the original tensor had in the reference basis:

\[\tau^{ij} = S^{IJ}, \qquad e_{ij} = E_{IJ}, \qquad (\mathcal{L}_v \boldsymbol{\tau})^{ij} = \mathbb{C}^{IJKL}\,\dot{E}_{KL},\]

with \(\boldsymbol{\tau}\) the Kirchhoff stress, \(\mathbf{S}\) the second Piola-Kirchhoff stress, \(\mathbf{e}\) the Almansi and \(\mathbf{E}\) the Green-Lagrange strain. Pushing forward carries the basis \(\mathbf{G}_i\) to \(\mathbf{g}_i = \mathbf{F}\,\mathbf{G}_i\) and keeps the components; the familiar \(\mathbf{F}\,\mathbf{S}\,\mathbf{F}^T\) is that statement followed by “write the result in the lab”. Accordingly:

  • on a lab tensor, push_forward(F) returns lab components, as before;

  • on a tensor that has its own basis, it keeps the components and returns the convected basis \(\mathbf{F}\,\mathbf{A}\) (pull_back: \(\mathbf{F}^{-1}\mathbf{A}\)). Only the weight \(1/J\) of metric=True (Kirchhoff to Cauchy, a density) touches the numbers.

F = np.array([[1., 0.5, 0.], [0., 1., 0.], [0., 0., 1.]])    # simple shear
S = sim.Tensor2.stress(np.array([0., 80., 0., 0., 0., 0.]))  # PK2 components
tau = S.with_basis(sim.Basis.from_F(F))    # Kirchhoff stress, same numbers
tau.to_basis(None)                         # == S.push_forward(F, metric=False)
tau.trace()                                # 100.0 = g_22 * 80, not 80

The same holds for an active rotation: a lab tensor is rotated in the lab, a tensor with its own basis keeps its components while its basis vectors turn (\(\mathbf{A} \to \mathbf{Q}\,\mathbf{A}\)), which leaves the metric and every invariant unchanged.

Invariants with the metric

Contractions between dual variances need no metric and act on the components in any basis: \(\boldsymbol{\sigma} : \boldsymbol{\varepsilon} = \sigma^{ij} \varepsilon_{ij}\), \(\mathbb{L} : \boldsymbol{\varepsilon}\), the inverse of a stiffness. Contracting two indices of the same variance does. For contravariant components (covariant ones exchange \(\mathbf{g}\) and \(\mathbf{g}^{-1}\)):

\[\mathrm{tr}\,\mathbf{T} = g_{ij}\,T^{ij}, \qquad \mathrm{dev}\,\hat{\mathbf{T}} = \hat{\mathbf{T}} - \tfrac13\,(\mathrm{tr}\,\mathbf{T})\,\mathbf{g}^{-1}, \qquad \mathbf{a} : \mathbf{b} = \mathrm{tr}(\hat{\mathbf{a}}\,\mathbf{g}\,\hat{\mathbf{b}}\,\mathbf{g}),\]
\[\sigma_{eq} = \sqrt{\tfrac32\,\mathbf{s} : \mathbf{s}}, \qquad \det\mathbf{T} = \det\hat{\mathbf{T}}\,\det\mathbf{g}, \qquad \det(\hat{\mathbf{T}} - \lambda\,\mathbf{g}^{-1}) = 0 .\]

trace, dev, mises, norm, det, eigvals and % use these forms in a natural basis and the usual ones otherwise. The identity tensor is the metric itself: Tensor2.identity("stress", basis=b) has components \(g^{ij}\), Tensor2.identity("strain", basis=b) has \(g_{ij}\). For fourth-order tensors, Tensor4.identity, volumetric and deviatoric take the same basis argument; the stiffness-type identity becomes \(\tfrac12 (g^{ik} g^{jl} + g^{il} g^{jk})\) and the volumetric projector \(\tfrac13\,g^{ij} g^{kl}\).

Note

The Kelvin-Mandel form is an isometry only in an orthonormal basis. In a natural basis .mandel and .voigt still return the stored components, but eye(6) is no longer the stiffness-type identity and the plain Frobenius norm of the 6x6 is not the norm of the tensor.

Cost: with basis=None nothing changes. A basis costs one reference per tensor object, not per point; with_basis and the transports of a tensor with its own basis are free; to_basis costs one transport (the same kernel as push_forward); the metric of a natural basis is computed on first use and cached, and its invariants cost about one 3x3 product per point more than in the lab.

API reference

class simcoon.Basis(rotation=None, vectors=None, name=None)

The basis the components of a Tensor2 / Tensor4 are written in.

A tensor whose basis is None is expressed in the fixed orthonormal lab basis \(\mathbf{e}_i\) (the default, at no cost). A Basis holds the basis vectors \(\mathbf{g}_i\) through the matrix \(\mathbf{A}\) whose columns are their lab components. Two kinds:

  • orthonormal – built from a rotation, \(\mathbf{g}_i = \mathbf{R}\,\mathbf{e}_i\). The metric is the identity: every formula of the tensor classes holds as in the lab.

  • natural – any three independent vectors, typically the convected basis \(\mathbf{g}_i = \mathbf{F}\,\mathbf{G}_i\) (from_F). The metric \(g_{ij} = \mathbf{g}_i \cdot \mathbf{g}_j\), i.e. \(\mathbf{g} = \mathbf{A}^T \mathbf{A}\), enters the trace, the deviator, the norms and the invariants.

The variance of the components follows the tensor type: stress is contravariant (\(\boldsymbol{\sigma} = \sigma^{ij}\,\mathbf{g}_i \otimes \mathbf{g}_j\)), strain covariant (\(\boldsymbol{\varepsilon} = \varepsilon_{ij}\,\mathbf{g}^i \otimes \mathbf{g}^j\)), stiffness and compliance likewise on their four indices, concentration tensors mixed. Lab components are \(\mathbf{A}\,\hat{\boldsymbol{\sigma}}\, \mathbf{A}^T\) and \(\mathbf{A}^{-T}\,\hat{\boldsymbol{\varepsilon}}\, \mathbf{A}^{-1}\).

One basis is either single (shared by every tensor of a batch, stored once) or a batch of N bases, one per tensor. A basis is immutable; tensors derived from one another share it by reference.

The variance is a convention of the components, not a property of the tensor: it is a tag (t.variance) defaulted from the type, and t.to_variance(v) gives the components of the other variance (with the metric in a natural basis). Two-point tensors such as \(\mathbf{F}\) have one index in each configuration and no single basis: F is always a plain array of lab components, consumed by from_F and by the transports.

Parameters:
  • rotation (simcoon.Rotation or scipy.spatial.transform.Rotation, optional) – Orthonormal basis \(\mathbf{g}_i = \mathbf{R}\,\mathbf{e}_i\) (single or batch).

  • vectors (array_like, optional) – Natural basis: (3,3) matrix (or (N,3,3) batch) whose columns are the lab components of the basis vectors. The array is kept by reference, not copied: do not modify it afterwards.

  • name (str, optional) – A label shown in repr and in error messages. It is never used to compare two bases.

Examples

import numpy as np
import simcoon as sim

material = sim.Basis(rotation=sim.Rotation.from_euler('z', 30, degrees=True),
                     name="material")
sigma = sim.Tensor2.stress(np.array([100., 0., 0., 0., 0., 0.]))
sigma_m = sigma.to_basis(material)      # same tensor, material components
sigma_m.to_basis(None)                  # back to lab components

F = np.array([[1., 0.5, 0.], [0., 1., 0.], [0., 0., 1.]])
S = sim.Tensor2.stress(np.array([0., 80., 0., 0., 0., 0.]))   # PK2
tau = S.with_basis(sim.Basis.from_F(F))  # Kirchhoff: same components, convected basis
tau.trace()                              # uses the metric
property det

float or (N,).

Type:

Volume of the basis, \(\det \mathbf{A} = \sqrt{\det \mathbf{g}}\)

equals(other, tol=1e-12)

True if other is the same basis within tol (the name is ignored).

classmethod from_F(F, name=None)

Convected basis \(\mathbf{g}_i = \mathbf{F}\,\mathbf{e}_i\) of a deformation gradient.

Parameters:
  • F (array_like) – (3,3) deformation gradient or (N,3,3) batch, kept by reference. Its metric is the right Cauchy-Green tensor \(\mathbf{C} = \mathbf{F}^T \mathbf{F}\).

  • name (str, optional) – Label of the basis.

Returns:

Natural basis.

Return type:

Basis

property inverse_metric

(3,3) or (N,3,3).

Type:

Contravariant metric \(g^{ij}\)

property matrix

Basis matrix \(\mathbf{A}\) (columns = lab components of the basis vectors): (3,3) or (N,3,3). Returns a copy.

property metric

(3,3) or (N,3,3).

Type:

Covariant metric \(g_{ij} = \mathbf{g}_i \cdot \mathbf{g}_j\)

property name

Label of the basis (None if unnamed).

property orthonormal

True for a basis built from a rotation (identity metric).

property polar

math:mathbf{R} = mathbf{A},mathbf{U}^{-1} of the polar decomposition \(\mathbf{A} = \mathbf{R}\, \mathbf{U}\), as a Basis (self when already orthonormal). For from_F it is the frame turned by the rotation of \(\mathbf{F}\).

Type:

The orthonormal basis closest to this one

Type:

the rotation

property reciprocal

mathbf{g}^i cdot mathbf{g}_j = delta^i_j, as the columns of \(\mathbf{A}^{-T}\): (3,3) or (N,3,3). Covariant components live on them. Returns a copy.

Type:

Reciprocal (dual) basis vectors \(\mathbf{g}^i\),

Type:

math

property rotation

The rotation of an orthonormal basis, None for a natural basis.

property single

True for one basis, False for a batch of N bases.

property stretch

Right stretch of the basis, \(\mathbf{U} = \sqrt{\mathbf{g}}\) (the identity for an orthonormal basis): (3,3) or (N,3,3). For from_F it is the right stretch of \(\mathbf{F}\). Returns a copy.

class simcoon.Tensor2(data)

A 2nd-order tensor with a type tag driving the Voigt convention and rotation dispatch.

A single object transparently represents either one tensor or a batch (scipy Rotation style): the stored numpy array is (6,) for a single tensor and (N, 6) for a batch. The type is a string: "stress", "strain" or "symmetric" for a symmetric tensor stored as a Voigt vector (shear factors 2*e_ij for strain, s_ij otherwise), or "none" – no Voigt convention – for any 3x3 (F, R, L, PK1) stored as its 9 row-major components, with no Voigt vector, no variance and no transport; it accepts an orthonormal basis (both legs read in that frame), not a natural one. The type also selects the rotation/transport rules.

Construct through the typed factories (stress, strain, from_mat, from_voigt, from_mandel), never through Tensor2(array).

Examples

import numpy as np
import simcoon as sim

sigma = sim.Tensor2.stress(np.array([100., 50., 75., 0., 0., 0.]))
sigma.mises()          # von Mises equivalent stress
eps = sim.Tensor2.strain(np.random.randn(1000, 6) * 0.01)  # batch
len(eps), eps[0]       # 1000, single Tensor2
property basis

The Basis the components are written in; None for the lab basis.

classmethod concatenate(batches)

Join multiple batches and/or singles (all of the same type) into one batch.

Parts sharing one basis keep it; parts in different bases give a per-tensor basis; lab and non-lab parts cannot be mixed.

count(item)

Count occurrences of a single tensor in batch.

det()

Determinant of the tensor (of its mixed components \(T^i{}_j\)).

In a natural basis \(\det \mathbf{T} = \det \hat{\mathbf{T}}\, \det \mathbf{g}\) for contravariant components and \(\det \hat{\mathbf{T}} / \det \mathbf{g}\) for covariant ones.

Returns:

Scalar for a single tensor, (N,) for a batch.

Return type:

float or numpy.ndarray

dev()

Deviatoric part t - tr(t)/3 I.

Returns:

Deviatoric tensor(s), same type tag.

Return type:

Tensor2

eigvals()

Eigenvalues (principal values), in ascending order.

They are those of the mixed components: in a natural basis the generalized problem \(\det(\hat{\mathbf{T}} - \lambda\, \mathbf{g}^{-1}) = 0\) (contravariant) or \(\det(\hat{\mathbf{T}} - \lambda\,\mathbf{g}) = 0\) (covariant), solved in symmetric form through a Cholesky factor of the metric.

Returns:

(3,) for a single tensor, (N,3) for a batch.

Return type:

numpy.ndarray

classmethod from_columns(arr, type_str)

Create a batch from a column-major array (C++ interop).

Parameters:
  • arr (array_like) – (6, N) array, one Voigt vector per column (the simcoon C++ batch convention); (9, N) for type "none".

  • type_str (str) – One of "stress", "strain", "symmetric", "none".

Returns:

Batch of N tensors.

Return type:

Tensor2

classmethod from_list(tensors)

Stack a list of single tensors (all of the same type) into a batch.

classmethod from_mandel(v, type_str)

Create from a Kelvin-Mandel 6-vector (inverse of the mandel property).

Parameters:
  • v (array_like) – (6,) Kelvin-Mandel vector (sqrt(2) factor on shear terms, identical for stress and strain) or (N,6) batch.

  • type_str (str) – One of "stress", "strain", "symmetric" (type "none" has no Mandel vector).

Returns:

Tensor(s) of the requested type.

Return type:

Tensor2

classmethod from_mat(m, type_str)

Create from a 3x3 matrix representation with an explicit type.

Parameters:
  • m (array_like) – (3,3) matrix or (N,3,3) batch of matrices.

  • type_str (str) – One of "stress", "strain", "symmetric" (symmetric, 6 components) or "none" (any 3x3, 9 components).

Returns:

Tensor(s) of the requested type.

Return type:

Tensor2

classmethod from_tensor(t, n)

Broadcast a single tensor to a batch of size n (copies the data).

classmethod from_voigt(v, type_str)

Create from a Voigt 6-vector with an explicit type.

Parameters:
  • v (array_like) – (6,) Voigt vector or (N,6) batch. Shear components follow the type convention (2*e_ij for strain, s_ij for stress).

  • type_str (str) – One of "stress", "strain", "symmetric" (type "none" has no Voigt vector).

Returns:

Tensor(s) of the requested type.

Return type:

Tensor2

classmethod identity(type_str='stress', basis=None)

Create the identity tensor of the given type.

In the lab or an orthonormal basis its Voigt components are [1,1,1,0,0,0]. In a natural basis the identity is the metric: \(g^{ij}\) for a stress-typed tensor, \(g_{ij}\) for a strain-typed one.

Parameters:
  • type_str (str, optional) – Tensor type (default "stress").

  • basis (Basis or None, optional) – Basis of the result (default: lab). A batch basis gives a batch.

is_symmetric(tol=1e-12)

Check symmetry of the 3x3 matrix representation (single only).

Parameters:

tol (float, optional) – Absolute tolerance on the off-diagonal differences (default 1e-12).

Return type:

bool

property mandel

Kelvin-Mandel vector (sqrt2 on shear, identical for stress/strain): (6,) for single, (N,6) for batch. Returns a copy.

property mat

(3,3) for single, (N,3,3) for batch.

Type:

Matrix

mises()

Von Mises equivalent (type-aware).

Uses the stress definition sqrt(3/2 s_dev:s_dev) for stress/symmetric and the strain definition sqrt(2/3 e_dev:e_dev) for strain.

Returns:

Scalar for a single tensor, (N,) for a batch.

Return type:

float or numpy.ndarray

norm()

Frobenius norm sqrt(t_ij t_ij) (type-aware shear factors).

Returns:

Scalar for a single tensor, (N,) for a batch.

Return type:

float or numpy.ndarray

pull_back(F, metric=True)

Pull-back (current to reference configuration) via the deformation gradient.

Inverse of push_forward; same type-dependent transport and metric semantics.

Parameters:
  • F (array_like) – (3,3) deformation gradient, or (N,3,3) batch.

  • metric (bool, optional) – True (default) includes the J = det(F) factor.

Returns:

Transported tensor(s), same type tag.

Return type:

Tensor2

push_forward(F, metric=True)

Push-forward (reference to current configuration) via the deformation gradient.

Type-dependent transport: stress is fully contravariant (F s F^T, Piola with 1/J), strain fully covariant (F^-T e F^-1).

A push-forward is a transport that carries the basis along: the transported tensor has, in the convected basis \(\mathbf{g}_i = \mathbf{F}\,\mathbf{G}_i\), the components the original had in \(\mathbf{G}_i\). A lab tensor is returned in lab components (the transport is carried out); a tensor with its own basis keeps its components and gets the convected basis \(\mathbf{F}\,\mathbf{A}\) (only the 1/J weight of metric=True touches the numbers).

The variance is read from the type tag: the covariant transport is the one of the Green-Lagrange / Almansi pair (and the contravariant one of the second Piola-Kirchhoff / Kirchhoff pair). A logarithmic strain is not the transport of anything: on a strain-typed \(\ln \mathbf{V}\) the result has no meaning. F is a two-point tensor and is always given as lab components.

Parameters:
  • F (array_like) – (3,3) deformation gradient, or (N,3,3) batch (a single F is broadcast over a tensor batch).

  • metric (bool, optional) – True (default) includes the J = det(F) factor (proper Piola transformation); False is pure transport.

Returns:

Transported tensor(s), same type tag.

Return type:

Tensor2

rotate(R, active=True)

Rotate the tensor(s) (active) or the frame they are written in (passive).

Parameters:
  • R (simcoon.Rotation or scipy.spatial.transform.Rotation) – Rotation(s) to apply. On a batch, a single rotation is broadcast to all N tensors; a batch of N rotations is applied slice-wise.

  • active (bool, optional) – True (default) rotates the tensor: a new tensor \(\mathbf{Q}\,\mathbf{X}\,\mathbf{Q}^T\), R being a lab rotation. A lab tensor stays written in the lab; a tensor with its own basis keeps its components and has its basis vectors turned (\(\mathbf{A} \to \mathbf{Q}\,\mathbf{A}\)). False rotates the frame: the same tensor, with components \(\mathbf{Q}^T \hat{\mathbf{X}}\,\mathbf{Q}\) in the basis turned by R (\(\mathbf{A} \to \mathbf{A}\,\mathbf{Q}\), R read in the current basis). The result carries that basis.

Returns:

Same type tag; basis as described above.

Return type:

Tensor2

Examples

import numpy as np
import simcoon as sim
from scipy.spatial.transform import Rotation

sigma = sim.Tensor2.stress(np.array([100., 0., 0., 0., 0., 0.]))
R = Rotation.from_euler('z', 45, degrees=True)
sigma_rot = sigma.rotate(R)                  # another tensor, lab basis
sigma_45 = sigma.rotate(R, active=False)     # same tensor, basis turned by R
sigma_45.to_basis(None) == sigma             # True up to round-off
property single

True if this is a single tensor, False if batch.

classmethod strain(data)

Create strain tensor(s).

Parameters:

data (array_like) – (6,) Voigt vector (with 2*e_ij shear terms), (3,3) matrix, (N,6) Voigt batch, or (N,3,3) matrix batch.

Returns:

Strain-typed tensor (single or batch, matching the input shape).

Return type:

Tensor2

classmethod stress(data)

Create stress tensor(s).

Parameters:

data (array_like) – (6,) Voigt vector, (3,3) matrix, (N,6) Voigt batch, or (N,3,3) matrix batch.

Returns:

Stress-typed tensor (single or batch, matching the input shape).

Return type:

Tensor2

to_basis(basis=None)

The same tensor, its components re-expressed in another basis.

With \(\mathbf{M} = \mathbf{A}_{new}^{-1} \mathbf{A}_{old}\), contravariant components become \(\mathbf{M}\,\hat{\mathbf{T}}\, \mathbf{M}^T\) and covariant ones \(\mathbf{M}^{-T}\, \hat{\mathbf{T}}\,\mathbf{M}^{-1}\), on each pair of indices.

Parameters:

basis (Basis or None, optional) – Target basis; None (default) gives the lab components.

Returns:

Same tensor and type, components in basis.

Return type:

Tensor2 or Tensor4

to_variance(variance)

The same tensor with components of another variance (index raising / lowering).

In the lab or an orthonormal basis the numbers do not change: the tag does. In a natural basis one pair of indices is lowered with the metric, \(T_{ij} = g_{ik}\,T^{kl}\,g_{lj}\), or raised with its inverse. The Voigt shear factors stay those of the type. On a "symmetric" Tensor2 whose variance is not set yet, this declares it.

Parameters:

variance (str or tuple of str) – "contravariant" / "covariant"; for a Tensor4 the pair (output, input).

Returns:

Same tensor, type and basis; components and tag of the new variance.

Return type:

Tensor2 or Tensor4

trace()

Trace t_kk.

Returns:

Scalar for a single tensor, (N,) for a batch.

Return type:

float or numpy.ndarray

property type

Type string.

property variance

"contravariant" or "covariant" for a Tensor2 (None when the type has none: "symmetric" until declared, "none"), a pair (output, input) for a Tensor4. Defaults to the one of the type.

Type:

Variance of the components

property voigt

(6,) for single, (N,6) for batch. Returns a copy. Type "none" (any 3x3) has no Voigt vector: use mat.

Type:

Voigt vector

property voigt_T

(6, N) transposed Voigt array (batch only, for C++ interop).

property vtype

Alias for type (backward compatibility).

with_basis(basis)

The tensor with the same components and another basis (a transport).

The numbers are kept and the basis vectors replaced: the result is a different tensor. Giving the convected basis of F to second Piola-Kirchhoff components yields the Kirchhoff stress, \(\tau^{ij} = S^{IJ}\); likewise \(e_{ij} = E_{IJ}\) for the Green-Lagrange / Almansi pair. No arithmetic, no copy.

Parameters:

basis (Basis or None) – The new basis (None = lab).

Returns:

Same components and type, new basis.

Return type:

Tensor2 or Tensor4

classmethod zeros(type_str='stress')

Create a single zero tensor of the given type (default "stress").

class simcoon.Tensor4(data)

A 4th-order tensor with a type tag driving rotation dispatch and Voigt factors.

A single object transparently represents either one tensor or a batch: the stored numpy array is (6,6) (engineering Voigt) for a single tensor and (N,6,6) for a batch. The type is a string: "stiffness", "compliance", "strain_concentration", "stress_concentration", or "generic"; it selects the Kelvin-Mandel congruence factors and the rotation/transport rules, and is propagated through operations (e.g. stiffness.inverse() is a compliance).

The C++ backend works internally in the Kelvin-Mandel convention, so contraction, inverse and composition are plain 6x6 linear algebra; the engineering Voigt form is what mat / voigt expose.

Construct through the typed factories (stiffness, compliance, strain_concentration, stress_concentration, from_mat, from_mandel), never through Tensor4(array).

Examples

import numpy as np
import simcoon as sim

L = sim.Tensor4.stiffness(sim.L_iso([70000, 0.3], 'Enu'))
eps = sim.Tensor2.strain(np.array([0.01, -0.003, -0.003, 0.005, 0., 0.]))
sigma = L @ eps                  # contraction -> stress Tensor2
M = L.inverse()                  # compliance Tensor4

Note

The underlying C++ tensor4 class uses a lazy mutable Fastor cache that is not thread-safe. However, the Python Tensor4 class creates fresh C++ objects for each operation (via _to_cpp()), so Python-level Tensor4 instances are safe to use from multiple threads (subject to the GIL).

property basis

The Basis the components are written in; None for the lab basis.

classmethod compliance(data)

Create compliance tensor(s) from an engineering Voigt 6x6.

Parameters:

data (array_like) – (6,6) matrix (e.g. from sim.M_iso) or (N,6,6) batch.

Return type:

Tensor4

classmethod concatenate(batches)

Join multiple batches and/or singles (all of the same type) into one batch.

Parts sharing one basis keep it; parts in different bases give a per-tensor basis; lab and non-lab parts cannot be mixed.

contract(t)

Double contraction with a Tensor2 (also available as L @ t).

The output type follows the tensor4 type: stiffness and stress_concentration produce a stress, compliance and strain_concentration produce a strain.

Parameters:

t (Tensor2) – Single or batch. Single ⊗ batch combinations broadcast; a single Tensor4 contracted with a batch Tensor2 collapses to one BLAS matrix-matrix product (the fast shared-tangent path).

Returns:

Contracted tensor(s) with the inferred type.

Return type:

Tensor2

count(item)

Count occurrences of a single tensor in batch.

classmethod deviatoric(type_str='stiffness', basis=None)

Create the deviatoric projector K = I - J of the given type (in basis, default lab).

classmethod from_list(tensors)

Stack a list of single tensors (all of the same type) into a batch.

classmethod from_mandel(m, type_str)

Create from a Kelvin-Mandel 6x6 (inverse of the mandel property).

Parameters:
  • m (array_like) – (6,6) Kelvin-Mandel matrix (per-type sqrt(2) congruence factors) or (N,6,6) batch.

  • type_str (str) – One of "stiffness", "compliance", "strain_concentration", "stress_concentration", "generic".

Return type:

Tensor4

classmethod from_mat(m, type_str)

Create from an engineering Voigt 6x6 with an explicit type.

Parameters:
  • m (array_like) – (6,6) matrix or (N,6,6) batch.

  • type_str (str) – One of "stiffness", "compliance", "strain_concentration", "stress_concentration", "generic".

Return type:

Tensor4

classmethod from_tensor(t, n)

Broadcast a single tensor to a batch of size n (copies the data).

classmethod from_voigt(v, type_str)

Alias for from_mat (explicit counterpart of from_mandel).

classmethod identity(type_str='stiffness', basis=None)

Create the identity tensor of the given type (default "stiffness").

For every type the identity contracts to the unchanged field (I : t == t); in the lab or an orthonormal basis its Kelvin-Mandel form is eye(6). In a natural basis (basis=) the stiffness-type identity is \(\tfrac12 (g^{ik} g^{jl} + g^{il} g^{jk})\) and the compliance-type one the same with \(g_{ij}\); only the concentration (mixed) types keep eye(6).

inverse()

Invert the tensor (plain 6x6 inverse in Kelvin-Mandel).

The type follows the algebra: stiffness ↔ compliance, concentration types keep their type, generic stays generic.

Returns:

Inverse tensor(s) with the inferred type.

Return type:

Tensor4

property mandel

Kelvin-Mandel 6x6 (per-type sqrt2 congruence; identity = eye(6) for every type): (6,6) single, (N,6,6) batch. Returns a copy.

property mat

(6,6) single, (N,6,6) batch. Returns a copy.

Type:

6x6 matrix

pull_back(F, metric=True)

Pull-back (current to reference configuration) via the deformation gradient.

Inverse of push_forward; same type-dependent transport and metric semantics.

Parameters:
  • F (array_like) – (3,3) deformation gradient, or (N,3,3) batch.

  • metric (bool, optional) – True (default) includes the J = det(F) factor.

Returns:

Transported tensor(s), same type tag.

Return type:

Tensor4

push_forward(F, metric=True)

Push-forward (reference to current configuration) via the deformation gradient.

Type-dependent transport: stiffness is fully contravariant (F⊗F : L : F^T⊗F^T with 1/J), compliance fully covariant. For a lab tensor, concentration types raise (mixed variance: no transport implemented on lab components). A tensor with its own basis (any type) keeps its components and gets the convected basis F.A, as for Tensor2.push_forward.

Parameters:
  • F (array_like) – (3,3) deformation gradient, or (N,3,3) batch (a single F is broadcast over a tensor batch).

  • metric (bool, optional) – True (default) includes the J = det(F) factor; False is pure transport.

Returns:

Transported tensor(s), same type tag.

Return type:

Tensor4

rotate(R, active=True)

Rotate the tensor(s) (active) or the frame they are written in (passive).

Same rules as Tensor2.rotate, with the type-dependent Voigt congruence.

Parameters:
  • R (simcoon.Rotation or scipy.spatial.transform.Rotation) – Rotation(s) to apply. On a batch, a single rotation is broadcast; a batch of N rotations is applied slice-wise.

  • active (bool, optional) – True (default) rotates the tensor; False the frame, and the result carries the turned basis.

Returns:

Same type tag.

Return type:

Tensor4

property single

True if this is a single tensor, False if batch.

classmethod stiffness(data)

Create stiffness tensor(s) from an engineering Voigt 6x6.

Parameters:

data (array_like) – (6,6) matrix (e.g. from sim.L_iso) or (N,6,6) batch.

Return type:

Tensor4

classmethod strain_concentration(data)

Create strain concentration tensor(s) (De = A : D maps, e.g. sim.A_R(F)).

Uses the engineering Voigt convention: the identity concentration is eye(6) and A.contract(strain) returns a strain.

Parameters:

data (array_like) – (6,6) matrix or (N,6,6) batch.

Return type:

Tensor4

classmethod stress_concentration(data)

Create stress concentration tensor(s) (s_local = B : s maps).

Parameters:

data (array_like) – (6,6) matrix or (N,6,6) batch.

Return type:

Tensor4

to_basis(basis=None)

The same tensor, its components re-expressed in another basis.

With \(\mathbf{M} = \mathbf{A}_{new}^{-1} \mathbf{A}_{old}\), contravariant components become \(\mathbf{M}\,\hat{\mathbf{T}}\, \mathbf{M}^T\) and covariant ones \(\mathbf{M}^{-T}\, \hat{\mathbf{T}}\,\mathbf{M}^{-1}\), on each pair of indices.

Parameters:

basis (Basis or None, optional) – Target basis; None (default) gives the lab components.

Returns:

Same tensor and type, components in basis.

Return type:

Tensor2 or Tensor4

to_variance(variance)

The same tensor with components of another variance (index raising / lowering).

In the lab or an orthonormal basis the numbers do not change: the tag does. In a natural basis one pair of indices is lowered with the metric, \(T_{ij} = g_{ik}\,T^{kl}\,g_{lj}\), or raised with its inverse. The Voigt shear factors stay those of the type. On a "symmetric" Tensor2 whose variance is not set yet, this declares it.

Parameters:

variance (str or tuple of str) – "contravariant" / "covariant"; for a Tensor4 the pair (output, input).

Returns:

Same tensor, type and basis; components and tag of the new variance.

Return type:

Tensor2 or Tensor4

property type

Type string.

property variance

"contravariant" or "covariant" for a Tensor2 (None when the type has none: "symmetric" until declared, "none"), a pair (output, input) for a Tensor4. Defaults to the one of the type.

Type:

Variance of the components

property voigt

Alias for mat.

classmethod volumetric(type_str='stiffness', basis=None)

Create the volumetric (spherical) projector J = 1/3 I⊗I of the given type.

In a natural basis (basis=) the stiffness-type projector is \(\tfrac13 g^{ij} g^{kl}\).

with_basis(basis)

The tensor with the same components and another basis (a transport).

The numbers are kept and the basis vectors replaced: the result is a different tensor. Giving the convected basis of F to second Piola-Kirchhoff components yields the Kirchhoff stress, \(\tau^{ij} = S^{IJ}\); likewise \(e_{ij} = E_{IJ}\) for the Green-Lagrange / Almansi pair. No arithmetic, no copy.

Parameters:

basis (Basis or None) – The new basis (None = lab).

Returns:

Same components and type, new basis.

Return type:

Tensor2 or Tensor4

classmethod zeros(type_str='stiffness')

Create a single zero tensor of the given type (default "stiffness").

simcoon.dyadic(a, b)

Dyadic (outer) product C_ijkl = a_ij b_kl of two single Tensor2.

Parameters:
Returns:

Stiffness-typed tensor.

Return type:

Tensor4

simcoon.auto_dyadic(a)

Dyadic product of a single Tensor2 with itself: C_ijkl = a_ij a_kl -> stiffness Tensor4.

simcoon.sym_dyadic(a, b)

Symmetric Voigt dyadic product C = v(a) v(b)^T of two single Tensor2 -> stiffness Tensor4.

simcoon.auto_sym_dyadic(a)

Symmetric Voigt dyadic product of a single Tensor2 with itself -> stiffness Tensor4.

simcoon.double_contract(a, b)

Double contraction A_ij B_ij of two Tensor2 (single or batch).

Parameters:
  • a (Tensor2) – Single or batch; singles broadcast against batches.

  • b (Tensor2) – Single or batch; singles broadcast against batches.

Returns:

(N,) values ((1,) if both are single).

Return type:

numpy.ndarray