pub struct Tensor3<const M: usize, const N: usize> { /* private fields */ }Expand description
Defines a third-order tensor in R³×R³×R³
The matrix representation of Tensor3 results in a rectangular matrix. Therefore, two matrices with max dimensions DIM×3 or 3×DIM are considered here, where DIM (the leading dimension) is one of 4, 6, or 9. For a third-order tensor with indices ijk, the cases are:
Case A: ij-pairwise => (ij)k => (m)k => (DIM×3)
Case B: jk-pairwise => i(jk) => i(n) => (3×DIM)Given u, T, and H as first-, second-, and third-order tensors, the main operations involving a third-order tensor are:
Case A (ij)k => T = H · u or u = T : H
Case B i(jk) => u = H : T or T = u · HIn index notation (with i,j,k = 1…3):
Case A (ij)k => Tᵢⱼ = Σ H₍ᵢⱼ₎ₖ uₖ or uₖ = Σ Σ Tᵢⱼ H₍ᵢⱼ₎ₖ
k i j
Case B i(jk) => uᵢ = Σ Σ Hᵢ₍ⱼₖ₎ Tⱼₖ or Tⱼₖ = Σ uᵢ Hᵢ₍ⱼₖ₎
j k iThe matrix representations associated with the two cases are (with m,n = 1…DIM and DIM = {4,6,9}):
Case A (m)k => Tₘ = Σ H₍ₘ₎ₖ uₖ or uₖ = Σ Tₘ H₍ₘ₎ₖ
k m
Case B i(n) => uᵢ = Σ Hᵢ₍ₙ₎ Tₙ or Tₙ = Σ uᵢ Hᵢ₍ₙ₎
n iNote that the first-order tensors (vectors) are always given by the standard
components in 3D. All functions here require vectors such as [u] = {u0, u1, u2}.
§Standard and Kelvin-Mandel components
The methods of this struct follow a naming convention that distinguishes
between the standard (Cartesian) components Hᵢⱼₖ and the Kelvin-Mandel
components stored internally:
- Methods dealing with standard components carry the
stdqualifier in their names (e.g., Tensor3::from_std_matrix, Tensor3::get_std, Tensor3::as_std_matrix, Tensor3::sym_set_std). - Methods dealing directly with the Kelvin-Mandel components carry no qualifier (e.g., Tensor3::get, Tensor3::set, Tensor3::set_tensor, Tensor3::update).
Internally, the components are converted to the Kelvin-Mandel basis as follows.
The Kelvin-Mandel components Ĥijk are calculated from the standard components Hijk using the following expression for Case A:
Case A:
⎧ Hijk if i = j
Ĥijk = ⎨ (Hijk + Hjik) / √2 if i < j
⎩ (Hjik - Hijk) / √2 if i > jThe Kelvin-Mandel components Ĥijk are calculated from the standard components Hijk using the following expression for Case B:
Case B:
⎧ Hijk if j = k
Ĥijk = ⎨ (Hijk + Hikj) / √2 if j < k
⎩ (Hikj - Hijk) / √2 if j > kIn Case A, minor-symmetry means Hijk = Hjik. Then, the mapping simplifies to:
Case A:
⎧ Hijk if i = j
Ĥijk = ⎨ Hijk √2 if i < j
⎩ 0 if i > jIn Case B, minor-symmetry means Hijk = Hikj. Then, the mapping simplifies to:
Case B:
⎧ Hijk if j = k
Ĥijk = ⎨ Hijk √2 if j < k
⎩ 0 if j > kThe components are organized in matrices:
- For Case A, the order of row indices, pairs (i,j) in (i,j,k), follow the same order used for Tensor2.
- For Case B, the order of column indices, pairs (j,k) in (i,j,k), follow the same order as the one for Tensor2.
The matrices are illustrated as follows.
max(DIM) = 9:
Case A:
0 0 0 1 0 2
-----------------
0 │ Ĥ000 Ĥ001 Ĥ002
1 │ Ĥ110 Ĥ111 Ĥ112
2 │ Ĥ220 Ĥ221 Ĥ222
│
3 │ Ĥ010 Ĥ011 Ĥ012
4 │ Ĥ120 Ĥ121 Ĥ122
5 │ Ĥ020 Ĥ021 Ĥ022
│
6 │ Ĥ100 Ĥ101 Ĥ102
7 │ Ĥ210 Ĥ211 Ĥ212
8 │ Ĥ200 Ĥ201 Ĥ202
-----------------
8 0 8 1 8 2Case B:
0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8
---------------------------------------------
0 │ Ĥ000 Ĥ011 Ĥ022 Ĥ001 Ĥ012 Ĥ002 Ĥ010 Ĥ021 Ĥ020
1 │ Ĥ100 Ĥ111 Ĥ122 Ĥ101 Ĥ112 Ĥ102 Ĥ110 Ĥ121 Ĥ120
2 │ Ĥ200 Ĥ211 Ĥ222 Ĥ201 Ĥ212 Ĥ202 Ĥ210 Ĥ221 Ĥ220
---------------------------------------------
2 0 2 1 2 2 2 3 2 4 2 5 2 6 2 7 2 8max(DIM) = 6:
Case A:
0 0 0 1 0 2
--------------------------
0 │ H000 H001 H002
1 │ H110 H111 H112
2 │ H220 H221 H222
│
3 │ H010*√2 H011*√2 H012*√2
4 │ H120*√2 H121*√2 H122*√2
5 │ H020*√2 H021*√2 H022*√2
--------------------------
5 0 5 1 5 2Case B:
0 0 0 1 0 2 0 3 0 4 0 5
---------------------------------------
0 │ H000 H011 H022 H001*√2 H012*√2 H002*√2
1 │ H100 H111 H122 H101*√2 H112*√2 H102*√2
2 │ H200 H211 H222 H201*√2 H212*√2 H202*√2
---------------------------------------
2 0 2 1 2 2 2 3 2 4 2 5max(DIM) = 4:
Case A:
0 0 0 1 0 2
--------------------------
0 │ H000 H001 H002
1 │ H110 H111 H112
2 │ H220 H221 H222
│
3 │ H010*√2 H011*√2 H012*√2
--------------------------
3 0 3 1 3 2Case B:
0 0 0 1 0 2 0 3
-----------------------
0 │ H000 H011 H022 H001*√2
1 │ H100 H111 H122 H101*√2
2 │ H200 H211 H222 H201*√2
-----------------------
2 0 2 1 2 2 2 3Implementations§
Source§impl<const M: usize, const N: usize> Tensor3<M, N>
impl<const M: usize, const N: usize> Tensor3<M, N>
Sourcepub fn set(&mut self, m: usize, n: usize, value: f64)
pub fn set(&mut self, m: usize, n: usize, value: f64)
Sets the (m,n) component of the Kelvin-Mandel matrix
§Input
m– the row indexn– the column indexvalue– the value to set
§Panics
A panic will occur if the indices are out of range.
§Examples
use russell_tensor::{Tensor3};
let mut dd = Tensor3::<9, 3>::new();
dd.set(0, 0, 123.0);
assert_eq!(dd.get(0, 0), 123.0);Sourcepub fn add(&mut self, m: usize, n: usize, value: f64)
pub fn add(&mut self, m: usize, n: usize, value: f64)
Adds a value to the (m,n) component of the Kelvin-Mandel matrix
§Input
m– the row indexn– the column indexvalue– the value to be added
§Panics
A panic will occur if the indices are out of range.
§Examples
use russell_tensor::{Tensor3};
let mut dd = Tensor3::<9, 3>::new();
dd.set(0, 0, 123.0);
dd.add(0, 0, 321.0);
assert_eq!(dd.get(0, 0), 444.0);Sourcepub fn set_std_array(
&mut self,
inp: &[[[f64; 3]; 3]; 3],
) -> Result<(), StrError>
pub fn set_std_array( &mut self, inp: &[[[f64; 3]; 3]; 3], ) -> Result<(), StrError>
Sets this tensor from a nested array containing the standard components
§Input
inp– the standard Dijk components with respect to a Cartesian system
Sourcepub fn from_std_array(inp: &[[[f64; 3]; 3]; 3]) -> Result<Self, StrError>
pub fn from_std_array(inp: &[[[f64; 3]; 3]; 3]) -> Result<Self, StrError>
Creates a new Tensor3 constructed from a nested array containing the standard components
§Input
inp– the standard Dijk components with respect to a Cartesian system
§Examples
use russell_tensor::{Tensor3, StrError};
fn main() -> Result<(), StrError> {
let mut inp = [[[0.0; 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
inp[i][j][k] = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
}
}
}
let dd = Tensor3::<9, 3>::from_std_array(&inp)?;
assert_eq!(
format!("{:.0}", dd.as_std_matrix()),
"┌ ┐\n\
│ 111 112 113 │\n\
│ 221 222 223 │\n\
│ 331 332 333 │\n\
│ 121 122 123 │\n\
│ 231 232 233 │\n\
│ 131 132 133 │\n\
│ 211 212 213 │\n\
│ 321 322 323 │\n\
│ 311 312 313 │\n\
└ ┘"
);
Ok(())
}Sourcepub fn set_std_matrix<'a, S>(&mut self, inp: &'a S) -> Result<(), StrError>
pub fn set_std_matrix<'a, S>(&mut self, inp: &'a S) -> Result<(), StrError>
Sets this tensor from a matrix with standard components
§Input
inp– the standard matrix of components with respect to a Cartesian system. The matrix must be 9x3 for Case A or 3x9 for Case B even if it corresponds to a minor-symmetric tensor.
§Panics
A panic will occur if the matrix has the incorrect dimensions:
- Case A: 9x3 required
- Case B: 3x9 required
Sourcepub fn from_std_matrix<'a, S>(inp: &'a S) -> Result<Self, StrError>
pub fn from_std_matrix<'a, S>(inp: &'a S) -> Result<Self, StrError>
Creates a new Tensor3 constructed from a matrix with standard components
§Input
inp– the standard matrix of components with respect to a Cartesian system. The matrix must be 9x3 for Case A or 3x9 for Case B even if it corresponds to a minor-symmetric tensor.
§Panics
A panic will occur if the matrix has the incorrect dimensions:
- Case A: 9x3 required
- Case B: 3x9 required
§Examples
use russell_tensor::{MN_TO_IJK_CASE_A, Tensor3, StrError};
fn main() -> Result<(), StrError> {
let mut inp = [[0.0; 3]; 9];
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
inp[m][n] = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
}
}
let dd = Tensor3::<9, 3>::from_std_matrix(&inp)?;
assert_eq!(
format!("{:.0}", dd.as_std_matrix()),
"┌ ┐\n\
│ 111 112 113 │\n\
│ 221 222 223 │\n\
│ 331 332 333 │\n\
│ 121 122 123 │\n\
│ 231 232 233 │\n\
│ 131 132 133 │\n\
│ 211 212 213 │\n\
│ 321 322 323 │\n\
│ 311 312 313 │\n\
└ ┘"
);
Ok(())
}Sourcepub fn get_std(&self, i: usize, j: usize, k: usize) -> f64
pub fn get_std(&self, i: usize, j: usize, k: usize) -> f64
Returns the (i,j,k) standard component
§Examples
use russell_lab::approx_eq;
use russell_tensor::{MN_TO_IJK_CASE_A, Tensor3, StrError};
fn main() -> Result<(), StrError> {
let mut inp = [[0.0; 3]; 9];
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
inp[m][n] = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
}
}
let dd = Tensor3::<9, 3>::from_std_matrix(&inp)?;
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
let val = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
approx_eq(dd.get_std(i,j,k), val, 1e-12);
}
}
Ok(())
}Sourcepub fn norm(&self) -> f64
pub fn norm(&self) -> f64
Calculates the Euclidean norm
norm(H) = √(H:H)The norm is computed with the Kelvin-Mandel components, which yields the same value as the Frobenius norm of the standard components because the Kelvin-Mandel mapping is norm-preserving.
§Examples
use russell_lab::approx_eq;
use russell_tensor::{Tensor3, StrError};
fn main() -> Result<(), StrError> {
// the permutation (Levi-Civita) tensor has norm = √6
let dd = Tensor3::<9, 3>::constant_permutation();
approx_eq(dd.norm(), f64::sqrt(6.0), 1e-13);
Ok(())
}Sourcepub fn scale(&mut self, alpha: f64)
pub fn scale(&mut self, alpha: f64)
Scales this tensor in-place
self := α self§Examples
use russell_tensor::{Tensor3, StrError};
fn main() -> Result<(), StrError> {
let mut dd = Tensor3::<9, 3>::new();
dd.set(0, 0, 1.0);
dd.set(1, 1, 2.0);
dd.set(2, 2, 3.0);
dd.scale(2.0);
assert_eq!(dd.get(0, 0), 2.0);
assert_eq!(dd.get(1, 1), 4.0);
assert_eq!(dd.get(2, 2), 6.0);
Ok(())
}Sourcepub fn scientific(
&self,
label: &str,
factor: f64,
width: usize,
precision: usize,
) -> String
pub fn scientific( &self, label: &str, factor: f64, width: usize, precision: usize, ) -> String
Returns the Kelvin-Mandel matrix in scientific notation
The returned String can be printed (e.g., println!("{}", ...)) or
saved to a log file.
§Input
label– a label (e.g., a description of the tensor)factor– a factor to multiply the components before printing (e.g., a unit conversion factor)width– the field width used to print each componentprecision– the number of digits after the decimal point
Sourcepub fn update(&mut self, alpha: f64, other: &Tensor3<M, N>)
pub fn update(&mut self, alpha: f64, other: &Tensor3<M, N>)
Adds another tensor to this one
self += α other§Examples
use russell_lab::approx_eq;
use russell_tensor::{MN_TO_IJK_CASE_A, Tensor3, StrError};
fn main() -> Result<(), StrError> {
let mut inp = [[0.0; 3]; 9];
for m in 0..4 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
inp[m][n] = 1.0;
}
}
let mut dd = Tensor3::<9, 3>::new();
let ee = Tensor3::<9, 3>::from_std_matrix(&inp)?;
dd.update(2.0, &ee);
assert_eq!(
format!("{:.0}", dd.as_std_matrix()),
"┌ ┐\n\
│ 2 2 2 │\n\
│ 2 2 2 │\n\
│ 2 2 2 │\n\
│ 2 2 2 │\n\
│ 0 0 0 │\n\
│ 0 0 0 │\n\
│ 0 0 0 │\n\
│ 0 0 0 │\n\
│ 0 0 0 │\n\
└ ┘"
);
Ok(())
}Sourcepub fn as_std_array(&self) -> Vec<Vec<Vec<f64>>>
pub fn as_std_array(&self) -> Vec<Vec<Vec<f64>>>
Returns a 3x3x3 array with the standard components
§Examples
use russell_lab::approx_eq;
use russell_tensor::{MN_TO_IJK_CASE_A, Tensor3, StrError};
fn main() -> Result<(), StrError> {
let mut inp = [[0.0; 3]; 9];
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
inp[m][n] = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
}
}
let dd = Tensor3::<9, 3>::from_std_matrix(&inp)?;
let arr = dd.as_std_array();
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
let val = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
approx_eq(arr[i][j][k], val, 1e-12);
}
}
Ok(())
}Sourcepub fn to_std_array(&self, dd: &mut [Vec<Vec<f64>>])
pub fn to_std_array(&self, dd: &mut [Vec<Vec<f64>>])
Converts this tensor to a 3x3x3 array with the standard components
§Panics
A panic will occur if the array is not 3x3x3, i.e., vec![vec![vec![0.0; 3]; 3]; 3]
§Examples
use russell_lab::approx_eq;
use russell_tensor::{MN_TO_IJK_CASE_A, Tensor3, StrError};
fn main() -> Result<(), StrError> {
let mut inp = [[0.0; 3]; 9];
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
inp[m][n] = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
}
}
let dd = Tensor3::<9, 3>::from_std_matrix(&inp)?;
let mut arr = vec![vec![vec![0.0; 3]; 3]; 3];
dd.to_std_array(&mut arr);
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
let val = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
approx_eq(arr[i][j][k], val, 1e-12);
}
}
Ok(())
}Sourcepub fn as_std_matrix(&self) -> Matrix
pub fn as_std_matrix(&self) -> Matrix
Returns a matrix with the standard components
Note: The matrix will have the standard components.
§Examples
use russell_tensor::{MN_TO_IJK_CASE_A, Tensor3, StrError};
fn main() -> Result<(), StrError> {
let mut inp = [[0.0; 3]; 9];
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
inp[m][n] = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
}
}
let dd = Tensor3::<9, 3>::from_std_matrix(&inp)?;
assert_eq!(
format!("{:.0}", dd.as_std_matrix()),
"┌ ┐\n\
│ 111 112 113 │\n\
│ 221 222 223 │\n\
│ 331 332 333 │\n\
│ 121 122 123 │\n\
│ 231 232 233 │\n\
│ 131 132 133 │\n\
│ 211 212 213 │\n\
│ 321 322 323 │\n\
│ 311 312 313 │\n\
└ ┘"
);
Ok(())
}Sourcepub fn to_std_matrix(&self, mat: &mut Matrix)
pub fn to_std_matrix(&self, mat: &mut Matrix)
Converts this tensor to a matrix with the standard components
§Input
mat– the resulting matrix
§Panics
A panic will occur if the matrix has the incorrect dimension.
§Examples
use russell_lab::Matrix;
use russell_tensor::{MN_TO_IJK_CASE_A, Tensor3, StrError};
fn main() -> Result<(), StrError> {
let mut inp = [[0.0; 3]; 9];
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
inp[m][n] = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
}
}
let dd = Tensor3::<9, 3>::from_std_matrix(&inp)?;
let mut mat = Matrix::new(9, 3);
dd.to_std_matrix(&mut mat);
assert_eq!(
format!("{:.0}", mat),
"┌ ┐\n\
│ 111 112 113 │\n\
│ 221 222 223 │\n\
│ 331 332 333 │\n\
│ 121 122 123 │\n\
│ 231 232 233 │\n\
│ 131 132 133 │\n\
│ 211 212 213 │\n\
│ 321 322 323 │\n\
│ 311 312 313 │\n\
└ ┘"
);
Ok(())
}Sourcepub fn sym_set_std(&mut self, i: usize, j: usize, k: usize, value: f64)
pub fn sym_set_std(&mut self, i: usize, j: usize, k: usize, value: f64)
Sets the (i,j,k) standard component of a minor-symmetric Tensor3
§Notes
- The tensor must be symmetric and (i,j) must correspond to the possible combination due to the space dimension, otherwise a panic may occur.
§Panics
- A panic will occur if the tensor is not symmetric; i.e., DIM = 9 instead of 4,6
- A panic will occur if the indices are out of range
§Examples
use russell_tensor::{MN_TO_IJK_CASE_A, Tensor3};
fn main() {
let mut dd = Tensor3::<4, 3>::new();
for m in 0..4 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
let value = (100 * (i + 1) + 10 * (j + 1) + (k + 1)) as f64;
dd.sym_set_std(i, j, k, value);
}
}
assert_eq!(
format!("{:.0}", dd.as_std_matrix()),
"┌ ┐\n\
│ 111 112 113 │\n\
│ 221 222 223 │\n\
│ 331 332 333 │\n\
│ 121 122 123 │\n\
│ 0 0 0 │\n\
│ 0 0 0 │\n\
│ 121 122 123 │\n\
│ 0 0 0 │\n\
│ 0 0 0 │\n\
└ ┘"
);
}Sourcepub fn set_tensor(&mut self, alpha: f64, other: &Tensor3<M, N>)
pub fn set_tensor(&mut self, alpha: f64, other: &Tensor3<M, N>)
Makes this tensor equal to another tensor, scaled by a factor alpha
self := α other§Examples
use russell_lab::mat_approx_eq;
use russell_tensor::{Tensor3, StrError};
fn main() -> Result<(), StrError> {
let data = &[
[ 1.0, 2.0, 3.0],
[ -1.0, -2.0, -3.0],
[ 2.0, 4.0, 6.0],
[ 10.0, 20.0, 30.0],
[ 0.0, 0.0, 0.0],
[ 0.0, 0.0, 0.0],
[ -2.0, -4.0, -6.0],
[ 0.0, 0.0, 0.0],
[ 0.0, 0.0, 0.0],
];
let dd = Tensor3::<9, 3>::from_std_matrix(data)?;
let mut ee = Tensor3::<9, 3>::new();
ee.set_tensor(1.0, &dd);
mat_approx_eq(&dd.as_std_matrix(), data, 1e-14);
Ok(())
}Sourcepub fn constant_permutation() -> Self
pub fn constant_permutation() -> Self
Returns the permutation (Levi-Civita) tensor
§Panics
A panic will occur if DIM != 9, i.e., if the tensor is not general
(Case A with M = 9 or Case B with N = 9).