use super::{
IJK_TO_MN_CASE_A, IJK_TO_MN_CASE_B, IJK_TO_MN_SYM_CASE_A, IJK_TO_MN_SYM_CASE_B, MN_TO_IJK_CASE_A, MN_TO_IJK_CASE_B,
SQRT_2,
};
use crate::StrError;
use russell_lab::{AsArray2D, Matrix, format_scientific};
use serde::{Deserialize, Serialize};
use std::cmp;
use std::fmt::{self, Write};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(transparent)]
#[serde(bound(
serialize = "[[f64; N]; M]: Serialize",
deserialize = "[[f64; N]; M]: Deserialize<'de>"
))]
pub struct Tensor3<const M: usize, const N: usize> {
pub(crate) mat: [[f64; N]; M],
}
impl<const M: usize, const N: usize> Tensor3<M, N> {
const VALIDATE_DIM: () = assert!(
((M == 4 || M == 6 || M == 9) && N == 3) || (M == 3 && (N == 4 || N == 6 || N == 9)),
"Tensor dimension must be such that (DIM,3) for Case A or (3,DIM) for case B with DIM = 4, 6, or 9."
);
pub fn new() -> Self {
let _ = Self::VALIDATE_DIM;
Tensor3 { mat: [[0.0; N]; M] }
}
#[inline]
pub fn get(&self, m: usize, n: usize) -> f64 {
self.mat[m][n]
}
#[inline]
pub fn set(&mut self, m: usize, n: usize, value: f64) {
self.mat[m][n] = value;
}
#[inline]
pub fn add(&mut self, m: usize, n: usize, value: f64) {
self.mat[m][n] += value;
}
pub fn set_std_array(&mut self, inp: &[[[f64; 3]; 3]; 3]) -> Result<(), StrError> {
if M > N {
if M == 4 || M == 6 {
let max = if M == 4 { 3 } else { 6 };
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
if i > j {
if inp[i][j][k] != inp[j][i][k] {
return Err("the input data does not correspond to a minor-symmetric tensor");
}
} else {
let (m, n) = IJK_TO_MN_CASE_A[i][j][k];
if m > max {
if inp[i][j][k] != 0.0 {
return Err(
"the input data does not correspond to a generalized plane minor-symmetric tensor",
);
}
continue;
} else if m < 3 {
self.set(m, n, inp[i][j][k]);
} else {
self.set(m, n, SQRT_2 * inp[i][j][k]);
}
}
}
}
}
} else {
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let (m, n) = IJK_TO_MN_CASE_A[i][j][k];
if i == j {
self.set(m, n, inp[i][j][k]);
} else if i < j {
self.set(m, n, (inp[i][j][k] + inp[j][i][k]) / SQRT_2);
} else if i > j {
self.set(m, n, (inp[j][i][k] - inp[i][j][k]) / SQRT_2);
}
}
}
}
}
} else {
if N == 4 || N == 6 {
let max = if N == 4 { 3 } else { 6 };
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
if j > k {
if inp[i][j][k] != inp[i][k][j] {
return Err("the input data does not correspond to a minor-symmetric tensor");
}
} else {
let (m, n) = IJK_TO_MN_CASE_B[i][j][k];
if n > max {
if inp[i][j][k] != 0.0 {
return Err(
"the input data does not correspond to a generalized plane minor-symmetric tensor",
);
}
continue;
} else if n < 3 {
self.set(m, n, inp[i][j][k]);
} else {
self.set(m, n, SQRT_2 * inp[i][j][k]);
}
}
}
}
}
} else {
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let (m, n) = IJK_TO_MN_CASE_B[i][j][k];
if j == k {
self.set(m, n, inp[i][j][k]);
} else if j < k {
self.set(m, n, (inp[i][j][k] + inp[i][k][j]) / SQRT_2);
} else if j > k {
self.set(m, n, (inp[i][k][j] - inp[i][j][k]) / SQRT_2);
}
}
}
}
}
}
Ok(())
}
pub fn from_std_array(inp: &[[[f64; 3]; 3]; 3]) -> Result<Self, StrError> {
let mut res = Tensor3::new();
res.set_std_array(inp)?;
Ok(res)
}
pub fn set_std_matrix<'a, S>(&mut self, inp: &'a S) -> Result<(), StrError>
where
S: AsArray2D<'a, f64>,
{
if M > N {
if M == 4 || M == 6 {
let max = if M == 4 { 3 } else { 6 };
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let (m, n) = IJK_TO_MN_CASE_A[i][j][k];
let (r, s) = IJK_TO_MN_CASE_A[j][i][k];
if i > j {
if inp.at(m, n) != inp.at(r, s) {
return Err("the input data does not correspond to a minor-symmetric tensor");
}
} else {
if m > max {
if inp.at(m, n) != 0.0 {
return Err(
"the input data does not correspond to a generalized plane minor-symmetric tensor",
);
}
continue;
} else if m < 3 {
self.set(m, n, inp.at(m, n));
} else {
self.set(m, n, SQRT_2 * inp.at(m, n));
}
}
}
}
}
} else {
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let (m, n) = IJK_TO_MN_CASE_A[i][j][k];
if i == j {
self.set(m, n, inp.at(m, n));
} else if i < j {
let (r, s) = IJK_TO_MN_CASE_A[j][i][k];
self.set(m, n, (inp.at(m, n) + inp.at(r, s)) / SQRT_2);
} else if i > j {
let (r, s) = IJK_TO_MN_CASE_A[j][i][k];
self.set(m, n, (inp.at(r, s) - inp.at(m, n)) / SQRT_2);
}
}
}
}
}
} else {
if N == 4 || N == 6 {
let max = if N == 4 { 3 } else { 6 };
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let (m, n) = IJK_TO_MN_CASE_B[i][j][k];
let (r, s) = IJK_TO_MN_CASE_B[i][k][j];
if j > k {
if inp.at(m, n) != inp.at(r, s) {
return Err("the input data does not correspond to a minor-symmetric tensor");
}
} else {
if n > max {
if inp.at(m, n) != 0.0 {
return Err(
"the input data does not correspond to a generalized plane minor-symmetric tensor",
);
}
continue;
} else if n < 3 {
self.set(m, n, inp.at(m, n));
} else {
self.set(m, n, SQRT_2 * inp.at(m, n));
}
}
}
}
}
} else {
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let (m, n) = IJK_TO_MN_CASE_B[i][j][k];
if j == k {
self.set(m, n, inp.at(m, n));
} else if j < k {
let (r, s) = IJK_TO_MN_CASE_B[i][k][j];
self.set(m, n, (inp.at(m, n) + inp.at(r, s)) / SQRT_2);
} else if j > k {
let (r, s) = IJK_TO_MN_CASE_B[i][k][j];
self.set(m, n, (inp.at(r, s) - inp.at(m, n)) / SQRT_2);
}
}
}
}
}
}
Ok(())
}
pub fn from_std_matrix<'a, S>(inp: &'a S) -> Result<Self, StrError>
where
S: AsArray2D<'a, f64>,
{
let mut res = Tensor3::new();
res.set_std_matrix(inp)?;
Ok(res)
}
pub fn get_std(&self, i: usize, j: usize, k: usize) -> f64 {
if M > N {
match M {
4 => {
let (m, n) = IJK_TO_MN_SYM_CASE_A[i][j][k];
if m > 3 {
0.0
} else if m < 3 {
self.get(m, n)
} else {
self.get(m, n) / SQRT_2
}
}
6 => {
let (m, n) = IJK_TO_MN_SYM_CASE_A[i][j][k];
if m < 3 { self.get(m, n) } else { self.get(m, n) / SQRT_2 }
}
_ => {
let (m, n) = IJK_TO_MN_CASE_A[i][j][k];
let val = self.get(m, n);
if i == j {
val
} else if i < j {
let (r, s) = IJK_TO_MN_CASE_A[j][i][k];
let other = self.get(r, s);
(val + other) / SQRT_2
} else {
let (r, s) = IJK_TO_MN_CASE_A[j][i][k];
let other = self.get(r, s);
(other - val) / SQRT_2
}
}
}
} else {
match N {
4 => {
let (m, n) = IJK_TO_MN_SYM_CASE_B[i][j][k];
if n > 3 {
0.0
} else if n < 3 {
self.get(m, n)
} else {
self.get(m, n) / SQRT_2
}
}
6 => {
let (m, n) = IJK_TO_MN_SYM_CASE_B[i][j][k];
if n < 3 { self.get(m, n) } else { self.get(m, n) / SQRT_2 }
}
_ => {
let (m, n) = IJK_TO_MN_CASE_B[i][j][k];
let val = self.get(m, n);
if j == k {
val
} else if j < k {
let (r, s) = IJK_TO_MN_CASE_B[i][k][j];
let other = self.get(r, s);
(val + other) / SQRT_2
} else {
let (r, s) = IJK_TO_MN_CASE_B[i][k][j];
let other = self.get(r, s);
(other - val) / SQRT_2
}
}
}
}
}
pub fn norm(&self) -> f64 {
let mut sm = 0.0;
for m in 0..M {
for n in 0..N {
let v = self.get(m, n);
sm += v * v;
}
}
f64::sqrt(sm)
}
#[inline]
pub fn scale(&mut self, alpha: f64) {
for m in 0..M {
for n in 0..N {
self.mat[m][n] *= alpha;
}
}
}
pub fn scientific(&self, label: &str, factor: f64, width: usize, precision: usize) -> String {
let mut buf = String::new();
writeln!(&mut buf, "{} =", label).unwrap();
writeln!(&mut buf, "┌{:1$}┐", " ", N * width + 1).unwrap();
for m in 0..M {
if m > 0 {
writeln!(&mut buf, " │").unwrap();
}
for n in 0..N {
if n == 0 {
write!(&mut buf, "│").unwrap();
}
let val = self.get(m, n) * factor;
write!(&mut buf, "{:>1$}", format_scientific(val, width, precision), width).unwrap();
}
}
writeln!(&mut buf, " │").unwrap();
writeln!(&mut buf, "└{:1$}┘", " ", N * width + 1).unwrap();
buf
}
pub fn update(&mut self, alpha: f64, other: &Tensor3<M, N>) {
for m in 0..M {
for n in 0..N {
self.set(m, n, self.get(m, n) + alpha * other.get(m, n));
}
}
}
pub fn as_std_array(&self) -> Vec<Vec<Vec<f64>>> {
let mut dd = vec![vec![vec![0.0; 3]; 3]; 3];
self.to_std_array(&mut dd);
dd
}
pub fn to_std_array(&self, dd: &mut [Vec<Vec<f64>>]) {
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
dd[i][j][k] = self.get_std(i, j, k);
}
}
}
}
pub fn as_std_matrix(&self) -> Matrix {
let mut mat = if M > N { Matrix::new(9, 3) } else { Matrix::new(3, 9) };
self.to_std_matrix(&mut mat);
mat
}
pub fn to_std_matrix(&self, mat: &mut Matrix) {
if M > N {
assert_eq!(mat.dims(), (9, 3), "Matrix dimensions must be (9, 3), Case A");
for m in 0..9 {
for n in 0..3 {
let (i, j, k) = MN_TO_IJK_CASE_A[m][n];
mat.set(m, n, self.get_std(i, j, k));
}
}
} else {
assert_eq!(mat.dims(), (3, 9), "Matrix dimensions must be (3, 9), Case B");
for m in 0..3 {
for n in 0..9 {
let (i, j, k) = MN_TO_IJK_CASE_B[m][n];
mat.set(m, n, self.get_std(i, j, k));
}
}
}
}
pub fn sym_set_std(&mut self, i: usize, j: usize, k: usize, value: f64) {
if M > N {
assert!(M != 9, "minor-symmetric case A requires M = 4,6");
let (m, n) = IJK_TO_MN_SYM_CASE_A[i][j][k];
if m < 3 {
self.set(m, n, value);
} else {
self.set(m, n, value * SQRT_2);
}
} else {
assert!(N != 9, "minor-symmetric case B requires N = 4,6");
let (m, n) = IJK_TO_MN_SYM_CASE_B[i][j][k];
if n < 3 {
self.set(m, n, value);
} else {
self.set(m, n, value * SQRT_2);
}
}
}
pub fn set_tensor(&mut self, alpha: f64, other: &Tensor3<M, N>) {
for m in 0..M {
for n in 0..N {
self.set(m, n, alpha * other.get(m, n));
}
}
}
pub fn constant_permutation() -> Self {
assert!(
M == 9 || N == 9,
"the permutation (Levi-Civita) tensor requires DIM = 9"
);
let pos_one = [(0, 1, 2), (1, 2, 0), (2, 0, 1)]; let neg_one = [(0, 2, 1), (1, 0, 2), (2, 1, 0)]; let mut std_array = [[[0.0; 3]; 3]; 3];
for (i, j, k) in pos_one {
std_array[i][j][k] = 1.0;
}
for (i, j, k) in neg_one {
std_array[i][j][k] = -1.0;
}
Tensor3::from_std_array(&std_array).unwrap()
}
}
impl<const M: usize, const N: usize> fmt::Display for Tensor3<M, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut width = 0;
let mut buf = String::new();
for i in 0..M {
for j in 0..N {
let val = self.get(i, j);
match f.precision() {
Some(v) => write!(&mut buf, "{:.1$}", val, v).unwrap(),
None => write!(&mut buf, "{}", val).unwrap(),
}
width = cmp::max(buf.chars().count(), width);
buf.clear();
}
}
width += 1;
writeln!(f, "┌{:1$}┐", " ", width * N + 1).unwrap();
for i in 0..M {
if i > 0 {
writeln!(f, " │").unwrap();
}
for j in 0..N {
if j == 0 {
write!(f, "│").unwrap();
}
let val = self.get(i, j);
match f.precision() {
Some(v) => write!(f, "{:>1$.2$}", val, width, v).unwrap(),
None => write!(f, "{:>1$}", val, width).unwrap(),
}
}
}
writeln!(f, " │").unwrap();
write!(f, "└{:1$}┘", " ", width * N + 1).unwrap();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{MN_TO_IJK_CASE_A, Tensor3};
use crate::{SQRT_2, SamplesTensor3};
use russell_lab::{Matrix, approx_eq, mat_approx_eq};
fn norm_from_std_array(arr: &[[[f64; 3]; 3]; 3]) -> f64 {
let mut sm = 0.0;
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
sm += arr[i][j][k] * arr[i][j][k];
}
}
}
f64::sqrt(sm)
}
#[test]
fn norm_works() {
let dd = Tensor3::<9, 3>::from_std_array(&SamplesTensor3::CASE_A_SAMPLE1).unwrap();
approx_eq(dd.norm(), norm_from_std_array(&SamplesTensor3::CASE_A_SAMPLE1), 1e-13);
let dd = Tensor3::<6, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_SAMPLE1).unwrap();
approx_eq(
dd.norm(),
norm_from_std_array(&SamplesTensor3::CASE_A_SYM_SAMPLE1),
1e-13,
);
let dd = Tensor3::<4, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_2D_SAMPLE1).unwrap();
approx_eq(
dd.norm(),
norm_from_std_array(&SamplesTensor3::CASE_A_SYM_2D_SAMPLE1),
1e-13,
);
let dd = Tensor3::<3, 9>::from_std_array(&SamplesTensor3::CASE_B_SAMPLE1).unwrap();
approx_eq(dd.norm(), norm_from_std_array(&SamplesTensor3::CASE_B_SAMPLE1), 1e-13);
let dd = Tensor3::<3, 6>::from_std_array(&SamplesTensor3::CASE_B_SYM_SAMPLE1).unwrap();
approx_eq(
dd.norm(),
norm_from_std_array(&SamplesTensor3::CASE_B_SYM_SAMPLE1),
1e-13,
);
let dd = Tensor3::<3, 4>::from_std_array(&SamplesTensor3::CASE_B_SYM_2D_SAMPLE1).unwrap();
approx_eq(
dd.norm(),
norm_from_std_array(&SamplesTensor3::CASE_B_SYM_2D_SAMPLE1),
1e-13,
);
}
#[test]
fn scale_works() {
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);
}
#[test]
fn scientific_works() {
let mut dd = Tensor3::<3, 4>::new();
dd.set(0, 0, 1.0);
dd.set(1, 1, 2.0);
dd.set(2, 2, 3.0);
assert_eq!(
dd.scientific("dd", 1.0, 10, 2),
"dd =\n\
┌ ┐\n\
│ 1.00E+00 0.00E+00 0.00E+00 0.00E+00 │\n\
│ 0.00E+00 2.00E+00 0.00E+00 0.00E+00 │\n\
│ 0.00E+00 0.00E+00 3.00E+00 0.00E+00 │\n\
└ ┘\n"
);
}
#[test]
fn new_set_and_get_work() {
let mut dd = Tensor3::<9, 3>::new();
dd.set(0, 0, 123.0);
assert_eq!(dd.get(0, 0), 123.0);
let mut dd = Tensor3::<6, 3>::new();
dd.set(0, 0, 123.0);
assert_eq!(dd.get(0, 0), 123.0);
let mut dd = Tensor3::<4, 3>::new();
dd.set(0, 0, 123.0);
assert_eq!(dd.get(0, 0), 123.0);
}
#[test]
fn from_std_array_fails_captures_errors() {
let res = Tensor3::<6, 3>::from_std_array(&SamplesTensor3::CASE_A_SAMPLE1);
assert_eq!(
res.err(),
Some("the input data does not correspond to a minor-symmetric tensor")
);
let res = Tensor3::<4, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_SAMPLE1);
assert_eq!(
res.err(),
Some("the input data does not correspond to a generalized plane minor-symmetric tensor")
);
}
#[test]
fn from_std_array_works() {
let dd = Tensor3::<9, 3>::from_std_array(&SamplesTensor3::CASE_A_SAMPLE1).unwrap();
for m in 0..9 {
for n in 0..3 {
assert_eq!(dd.get(m, n), SamplesTensor3::CASE_A_SAMPLE1_KELVIN_MATRIX[m][n]);
}
}
let dd = Tensor3::<6, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_SAMPLE1).unwrap();
for m in 0..6 {
for n in 0..3 {
assert_eq!(dd.get(m, n), SamplesTensor3::CASE_A_SYM_SAMPLE1_KELVIN_MATRIX[m][n]);
}
}
let dd = Tensor3::<4, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_2D_SAMPLE1).unwrap();
for m in 0..4 {
for n in 0..3 {
assert_eq!(dd.get(m, n), SamplesTensor3::CASE_A_SYM_2D_SAMPLE1_KELVIN_MATRIX[m][n]);
}
}
}
#[test]
fn from_std_matrix_fails_captures_errors() {
let mut inp = [[0.0; 3]; 9];
inp[3][0] = 1e-15;
let res = Tensor3::<6, 3>::from_std_matrix(&inp);
assert_eq!(
res.err(),
Some("the input data does not correspond to a minor-symmetric tensor")
);
inp[3][0] = 0.0;
inp[4][0] = 1.0;
inp[7][0] = 1.0;
let res = Tensor3::<4, 3>::from_std_matrix(&inp);
assert_eq!(
res.err(),
Some("the input data does not correspond to a generalized plane minor-symmetric tensor")
);
}
#[test]
fn get_and_set_work() {
let mut dd = Tensor3::<4, 3>::new();
assert_eq!(dd.get(0, 0), 0.0);
dd.set(0, 0, 2.0);
assert_eq!(dd.get(0, 0), 2.0);
}
#[test]
fn from_std_matrix_works() {
let dd = Tensor3::<9, 3>::from_std_matrix(&SamplesTensor3::CASE_A_SAMPLE1_STD_MATRIX).unwrap();
for m in 0..9 {
for n in 0..3 {
approx_eq(dd.get(m, n), SamplesTensor3::CASE_A_SAMPLE1_KELVIN_MATRIX[m][n], 1e-15);
}
}
let dd = Tensor3::<6, 3>::from_std_matrix(&SamplesTensor3::CASE_A_SYM_SAMPLE1_STD_MATRIX).unwrap();
for m in 0..6 {
for n in 0..3 {
approx_eq(
dd.get(m, n),
SamplesTensor3::CASE_A_SYM_SAMPLE1_KELVIN_MATRIX[m][n],
1e-14,
);
}
}
let dd = Tensor3::<4, 3>::from_std_matrix(&SamplesTensor3::CASE_A_SYM_2D_SAMPLE1_STD_MATRIX).unwrap();
for m in 0..4 {
for n in 0..3 {
approx_eq(
dd.get(m, n),
SamplesTensor3::CASE_A_SYM_2D_SAMPLE1_KELVIN_MATRIX[m][n],
1e-14,
);
}
}
}
#[test]
fn get_std_works() {
let dd = Tensor3::<9, 3>::from_std_array(&SamplesTensor3::CASE_A_SAMPLE1).unwrap();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(dd.get_std(i, j, k), SamplesTensor3::CASE_A_SAMPLE1[i][j][k], 1e-13);
}
}
}
let dd = Tensor3::<6, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_SAMPLE1).unwrap();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(dd.get_std(i, j, k), SamplesTensor3::CASE_A_SYM_SAMPLE1[i][j][k], 1e-14);
}
}
}
let dd = Tensor3::<4, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_2D_SAMPLE1).unwrap();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(
dd.get_std(i, j, k),
SamplesTensor3::CASE_A_SYM_2D_SAMPLE1[i][j][k],
1e-14,
);
}
}
}
}
#[test]
fn update_works() {
let mut dd = Tensor3::<4, 3>::new();
let ee = Tensor3::<4, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_2D_SAMPLE1).unwrap();
dd.update(2.0, &ee);
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(
dd.get_std(i, j, k),
2.0 * SamplesTensor3::CASE_A_SYM_2D_SAMPLE1[i][j][k],
1e-14,
);
}
}
}
}
#[test]
fn as_std_array_and_to_std_array_work() {
let dd = Tensor3::<9, 3>::from_std_array(&SamplesTensor3::CASE_A_SAMPLE1).unwrap();
let res = dd.as_std_array();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(res[i][j][k], SamplesTensor3::CASE_A_SAMPLE1[i][j][k], 1e-13);
}
}
}
let dd = Tensor3::<6, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_SAMPLE1).unwrap();
let res = dd.as_std_array();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(res[i][j][k], SamplesTensor3::CASE_A_SYM_SAMPLE1[i][j][k], 1e-14);
}
}
}
let dd = Tensor3::<4, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_2D_SAMPLE1).unwrap();
let res = dd.as_std_array();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(res[i][j][k], SamplesTensor3::CASE_A_SYM_2D_SAMPLE1[i][j][k], 1e-14);
}
}
}
}
#[test]
fn as_std_matrix_and_to_std_matrix_work() {
let dd = Tensor3::<9, 3>::from_std_array(&SamplesTensor3::CASE_A_SAMPLE1).unwrap();
let mat = dd.as_std_matrix();
for m in 0..9 {
for n in 0..3 {
approx_eq(mat.get(m, n), SamplesTensor3::CASE_A_SAMPLE1_STD_MATRIX[m][n], 1e-13);
}
}
let dd = Tensor3::<6, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_SAMPLE1).unwrap();
let mat = dd.as_std_matrix();
assert_eq!(mat.dims(), (9, 3));
for m in 0..9 {
for n in 0..3 {
approx_eq(
mat.get(m, n),
SamplesTensor3::CASE_A_SYM_SAMPLE1_STD_MATRIX[m][n],
1e-13,
);
}
}
let dd = Tensor3::<4, 3>::from_std_array(&SamplesTensor3::CASE_A_SYM_2D_SAMPLE1).unwrap();
let mat = dd.as_std_matrix();
assert_eq!(mat.dims(), (9, 3));
for m in 0..9 {
for n in 0..3 {
approx_eq(
mat.get(m, n),
SamplesTensor3::CASE_A_SYM_2D_SAMPLE1_STD_MATRIX[m][n],
1e-13,
);
}
}
}
#[test]
fn from_std_array_to_std_matrix_from_std_matrix_work() {
#[rustfmt::skip]
let data = &[
[
[ 18.0, 16.0, 14.0],
[ 36.0, 32.0, 28.0],
[ 54.0, 48.0, 42.0],
],
[
[ 72.0, 64.0, 56.0],
[ 90.0, 80.0, 70.0],
[108.0, 96.0, 84.0],
],
[
[126.0, 112.0, 98.0],
[144.0, 128.0, 112.0],
[162.0, 144.0, 126.0],
],
];
let dd = Tensor3::<9, 3>::from_std_array(data).unwrap();
let m1 = dd.as_std_matrix();
#[rustfmt::skip]
let correct = &[
[ 18.0, 16.0, 14.0],
[ 90.0, 80.0, 70.0],
[162.0, 144.0, 126.0],
[ 36.0, 32.0, 28.0],
[108.0, 96.0, 84.0],
[ 54.0, 48.0, 42.0],
[ 72.0, 64.0, 56.0],
[144.0, 128.0, 112.0],
[126.0, 112.0, 98.0],
];
mat_approx_eq(&m1, correct, 1e-13);
let ee = Tensor3::<9, 3>::from_std_matrix(correct).unwrap();
let m2 = ee.as_std_matrix();
mat_approx_eq(&m2, correct, 1e-13);
#[rustfmt::skip]
let data = &[
[
[ 6.0, 10.0, 12.0],
[24.0, 40.0, 48.0],
[36.0, 60.0, 72.0],
],
[
[24.0, 40.0, 48.0],
[12.0, 20.0, 24.0],
[30.0, 50.0, 60.0],
],
[
[36.0, 60.0, 72.0],
[30.0, 50.0, 60.0],
[18.0, 30.0, 36.0],
],
];
let dd = Tensor3::<6, 3>::from_std_array(data).unwrap();
let m1 = dd.as_std_matrix();
#[rustfmt::skip]
let correct = &[
[ 6.0, 10.0, 12.0],
[12.0, 20.0, 24.0],
[18.0, 30.0, 36.0],
[24.0, 40.0, 48.0],
[30.0, 50.0, 60.0],
[36.0, 60.0, 72.0],
[24.0, 40.0, 48.0],
[30.0, 50.0, 60.0],
[36.0, 60.0, 72.0],
];
mat_approx_eq(&m1, correct, 1e-13);
let ee = Tensor3::<6, 3>::from_std_matrix(correct).unwrap();
let m2 = ee.as_std_matrix();
mat_approx_eq(&m2, correct, 1e-13);
#[rustfmt::skip]
let data = &[
[
[ 6.0, 8.0, 0.0],
[24.0, 32.0, 0.0],
[ 0.0, 0.0, 0.0],
],
[
[24.0, 32.0, 0.0],
[12.0, 16.0, 0.0],
[ 0.0, 0.0, 0.0],
],
[
[ 0.0, 0.0, 0.0],
[ 0.0, 0.0, 0.0],
[18.0, 24.0, 0.0],
],
];
let dd = Tensor3::<4, 3>::from_std_array(data).unwrap();
let m1 = dd.as_std_matrix();
#[rustfmt::skip]
let correct = &[
[ 6.0, 8.0, 0.0],
[12.0, 16.0, 0.0],
[18.0, 24.0, 0.0],
[24.0, 32.0, 0.0],
[ 0.0, 0.0, 0.0],
[ 0.0, 0.0, 0.0],
[24.0, 32.0, 0.0],
[ 0.0, 0.0, 0.0],
[ 0.0, 0.0, 0.0],
];
mat_approx_eq(&m1, correct, 1e-13);
let ee = Tensor3::<4, 3>::from_std_matrix(correct).unwrap();
let m2 = ee.as_std_matrix();
mat_approx_eq(&m2, correct, 1e-13);
}
fn generate_dd_sym() -> Tensor3<6, 3> {
let mut dd = Tensor3::new();
for m in 0..6 {
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);
}
}
dd
}
#[test]
#[should_panic(expected = "minor-symmetric case A requires M = 4,6")]
fn sym_set_std_panics_on_non_sym() {
let mut dd = Tensor3::<9, 3>::new();
dd.sym_set_std(0, 0, 0, 0.0);
}
#[test]
#[should_panic(expected = "the len is 3 but the index is 3")]
fn sym_set_std_panics_on_incorrect_indices() {
let mut dd = Tensor3::<4, 3>::new();
dd.sym_set_std(0, 0, 3, 5.0);
}
#[test]
fn sym_set_std_works() {
let dd = generate_dd_sym();
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\
│ 121 122 123 │\n\
│ 231 232 233 │\n\
│ 131 132 133 │\n\
└ ┘"
);
}
#[test]
fn set_tensor_works() {
#[rustfmt::skip]
let dd = Tensor3::<9,3>::from_std_matrix(&[
[1.0, 1.0, 1.0],
[5.0, 5.0, 5.0],
[9.0, 9.0, 9.0],
[2.0, 2.0, 2.0],
[6.0, 6.0, 6.0],
[3.0, 3.0, 3.0],
[2.0, 2.0, 2.0],
[6.0, 6.0, 6.0],
[3.0, 3.0, 3.0],
]).unwrap();
let mut ee = Tensor3::<9, 3>::new();
ee.set_tensor(2.0, &dd);
#[rustfmt::skip]
let correct = Matrix::from(&[
[ 2.0, 2.0, 2.0],
[10.0, 10.0, 10.0],
[18.0, 18.0, 18.0],
[ 4.0, 4.0, 4.0],
[12.0, 12.0, 12.0],
[ 6.0, 6.0, 6.0],
[ 4.0, 4.0, 4.0],
[12.0, 12.0, 12.0],
[ 6.0, 6.0, 6.0],
]);
mat_approx_eq(&ee.as_std_matrix(), &correct, 1e-14);
}
fn generate_std_general() -> [[[f64; 3]; 3]; 3] {
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;
}
}
}
inp
}
fn generate_std_sym_case_b() -> [[[f64; 3]; 3]; 3] {
let mut inp = [[[0.0; 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let (a, b) = if j <= k { (j, k) } else { (k, j) };
inp[i][j][k] = (100 * (i + 1) + 10 * (a + 1) + (b + 1)) as f64;
}
}
}
inp
}
fn generate_std_sym_case_b_2d() -> [[[f64; 3]; 3]; 3] {
let mut inp = [[[0.0; 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let (a, b) = if j <= k { (j, k) } else { (k, j) };
if a == b || (a, b) == (0, 1) {
inp[i][j][k] = (100 * (i + 1) + 10 * (a + 1) + (b + 1)) as f64;
}
}
}
}
inp
}
#[test]
fn new_case_b_works() {
let _ = Tensor3::<3, 9>::new();
let _ = Tensor3::<3, 6>::new();
let _ = Tensor3::<3, 4>::new();
}
#[test]
fn from_std_array_case_b_fails_captures_errors() {
let res = Tensor3::<3, 6>::from_std_array(&SamplesTensor3::CASE_B_SAMPLE1);
assert_eq!(
res.err(),
Some("the input data does not correspond to a minor-symmetric tensor")
);
let res = Tensor3::<3, 4>::from_std_array(&SamplesTensor3::CASE_B_SYM_SAMPLE1);
assert_eq!(
res.err(),
Some("the input data does not correspond to a generalized plane minor-symmetric tensor")
);
}
#[test]
fn from_std_array_case_b_works() {
let dd = Tensor3::<3, 9>::from_std_array(&SamplesTensor3::CASE_B_SAMPLE1).unwrap();
for m in 0..3 {
for n in 0..9 {
assert_eq!(dd.get(m, n), SamplesTensor3::CASE_B_SAMPLE1_KELVIN_MATRIX[m][n]);
}
}
let dd = Tensor3::<3, 6>::from_std_array(&SamplesTensor3::CASE_B_SYM_SAMPLE1).unwrap();
for m in 0..3 {
for n in 0..6 {
assert_eq!(dd.get(m, n), SamplesTensor3::CASE_B_SYM_SAMPLE1_KELVIN_MATRIX[m][n]);
}
}
let dd = Tensor3::<3, 4>::from_std_array(&SamplesTensor3::CASE_B_SYM_2D_SAMPLE1).unwrap();
for m in 0..3 {
for n in 0..4 {
assert_eq!(dd.get(m, n), SamplesTensor3::CASE_B_SYM_2D_SAMPLE1_KELVIN_MATRIX[m][n]);
}
}
}
#[test]
fn from_std_matrix_case_b_works() {
let dd = Tensor3::<3, 9>::from_std_matrix(&SamplesTensor3::CASE_B_SAMPLE1_STD_MATRIX).unwrap();
for m in 0..3 {
for n in 0..9 {
approx_eq(dd.get(m, n), SamplesTensor3::CASE_B_SAMPLE1_KELVIN_MATRIX[m][n], 1e-15);
}
}
let dd = Tensor3::<3, 6>::from_std_matrix(&SamplesTensor3::CASE_B_SYM_SAMPLE1_STD_MATRIX).unwrap();
for m in 0..3 {
for n in 0..6 {
approx_eq(
dd.get(m, n),
SamplesTensor3::CASE_B_SYM_SAMPLE1_KELVIN_MATRIX[m][n],
1e-14,
);
}
}
let dd = Tensor3::<3, 4>::from_std_matrix(&SamplesTensor3::CASE_B_SYM_2D_SAMPLE1_STD_MATRIX).unwrap();
for m in 0..3 {
for n in 0..4 {
approx_eq(
dd.get(m, n),
SamplesTensor3::CASE_B_SYM_2D_SAMPLE1_KELVIN_MATRIX[m][n],
1e-14,
);
}
}
}
#[test]
fn get_std_case_b_works() {
let dd = Tensor3::<3, 9>::from_std_array(&SamplesTensor3::CASE_B_SAMPLE1).unwrap();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(dd.get_std(i, j, k), SamplesTensor3::CASE_B_SAMPLE1[i][j][k], 1e-13);
}
}
}
let dd = Tensor3::<3, 6>::from_std_array(&SamplesTensor3::CASE_B_SYM_SAMPLE1).unwrap();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(dd.get_std(i, j, k), SamplesTensor3::CASE_B_SYM_SAMPLE1[i][j][k], 1e-14);
}
}
}
let dd = Tensor3::<3, 4>::from_std_array(&SamplesTensor3::CASE_B_SYM_2D_SAMPLE1).unwrap();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(
dd.get_std(i, j, k),
SamplesTensor3::CASE_B_SYM_2D_SAMPLE1[i][j][k],
1e-14,
);
}
}
}
}
#[test]
fn update_case_b_works() {
let mut dd = Tensor3::<3, 4>::new();
let ee = Tensor3::<3, 4>::from_std_array(&SamplesTensor3::CASE_B_SYM_2D_SAMPLE1).unwrap();
dd.update(2.0, &ee);
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(
dd.get_std(i, j, k),
2.0 * SamplesTensor3::CASE_B_SYM_2D_SAMPLE1[i][j][k],
1e-14,
);
}
}
}
}
#[test]
fn as_std_array_and_to_std_array_case_b_work() {
let dd = Tensor3::<3, 9>::from_std_array(&SamplesTensor3::CASE_B_SAMPLE1).unwrap();
let res = dd.as_std_array();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(res[i][j][k], SamplesTensor3::CASE_B_SAMPLE1[i][j][k], 1e-13);
}
}
}
let dd = Tensor3::<3, 6>::from_std_array(&SamplesTensor3::CASE_B_SYM_SAMPLE1).unwrap();
let res = dd.as_std_array();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(res[i][j][k], SamplesTensor3::CASE_B_SYM_SAMPLE1[i][j][k], 1e-14);
}
}
}
let dd = Tensor3::<3, 4>::from_std_array(&SamplesTensor3::CASE_B_SYM_2D_SAMPLE1).unwrap();
let res = dd.as_std_array();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(res[i][j][k], SamplesTensor3::CASE_B_SYM_2D_SAMPLE1[i][j][k], 1e-14);
}
}
}
}
#[test]
fn as_std_matrix_and_to_std_matrix_case_b_work() {
let dd = Tensor3::<3, 9>::from_std_array(&SamplesTensor3::CASE_B_SAMPLE1).unwrap();
let mat = dd.as_std_matrix();
for m in 0..3 {
for n in 0..9 {
approx_eq(mat.get(m, n), SamplesTensor3::CASE_B_SAMPLE1_STD_MATRIX[m][n], 1e-13);
}
}
let dd = Tensor3::<3, 6>::from_std_array(&SamplesTensor3::CASE_B_SYM_SAMPLE1).unwrap();
let mat = dd.as_std_matrix();
assert_eq!(mat.dims(), (3, 9));
for m in 0..3 {
for n in 0..9 {
approx_eq(
mat.get(m, n),
SamplesTensor3::CASE_B_SYM_SAMPLE1_STD_MATRIX[m][n],
1e-13,
);
}
}
let dd = Tensor3::<3, 4>::from_std_array(&SamplesTensor3::CASE_B_SYM_2D_SAMPLE1).unwrap();
let mat = dd.as_std_matrix();
assert_eq!(mat.dims(), (3, 9));
for m in 0..3 {
for n in 0..9 {
approx_eq(
mat.get(m, n),
SamplesTensor3::CASE_B_SYM_2D_SAMPLE1_STD_MATRIX[m][n],
1e-13,
);
}
}
}
#[test]
fn sym_set_std_case_b_works() {
let mut dd = Tensor3::<3, 6>::new();
let inp = generate_std_sym_case_b();
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
if j <= k {
dd.sym_set_std(i, j, k, inp[i][j][k]);
}
}
}
}
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(dd.get_std(i, j, k), inp[i][j][k], 1e-13);
}
}
}
}
#[test]
fn from_std_matrix_case_b_symmetric2d_fails() {
let inp = generate_std_sym_case_b_2d();
let dd = Tensor3::<3, 4>::from_std_array(&inp).unwrap();
let mut mat = dd.as_std_matrix();
mat.set(0, 5, 5.0);
let res = Tensor3::<3, 4>::from_std_matrix(&mat);
assert_eq!(
res.err(),
Some("the input data does not correspond to a generalized plane minor-symmetric tensor")
);
}
#[test]
fn from_std_matrix_case_b_symmetric_fails() {
let inp = generate_std_sym_case_b();
let dd = Tensor3::<3, 6>::from_std_array(&inp).unwrap();
let mut mat = dd.as_std_matrix();
mat.set(0, 3, mat.get(0, 3) + 1.0);
let res = Tensor3::<3, 6>::from_std_matrix(&mat);
assert_eq!(
res.err(),
Some("the input data does not correspond to a minor-symmetric tensor")
);
}
#[test]
fn set_tensor_and_update_case_b_work() {
let inp = generate_std_general();
let dd = Tensor3::<3, 9>::from_std_array(&inp).unwrap();
let mut ee = Tensor3::<3, 9>::new();
ee.set_tensor(2.0, &dd);
for m in 0..3 {
for n in 0..9 {
approx_eq(ee.get(m, n), 2.0 * dd.get(m, n), 1e-13);
}
}
let mut ff = Tensor3::<3, 9>::new();
ff.update(1.0, &dd);
ff.update(2.0, &dd);
for m in 0..3 {
for n in 0..9 {
approx_eq(ff.get(m, n), 3.0 * dd.get(m, n), 1e-13);
}
}
}
#[test]
fn clone_and_serialize_work() {
let dd = generate_dd_sym();
let mut cloned = dd.clone();
cloned.set(0, 0, 999.0);
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\
│ 121 122 123 │\n\
│ 231 232 233 │\n\
│ 131 132 133 │\n\
└ ┘"
);
assert_eq!(
format!("{:.0}", cloned.as_std_matrix()),
"┌ ┐\n\
│ 999 112 113 │\n\
│ 221 222 223 │\n\
│ 331 332 333 │\n\
│ 121 122 123 │\n\
│ 231 232 233 │\n\
│ 131 132 133 │\n\
│ 121 122 123 │\n\
│ 231 232 233 │\n\
│ 131 132 133 │\n\
└ ┘"
);
let json = serde_json::to_string(&dd).unwrap();
assert!(!json.is_empty());
let from_json: Tensor3<6, 3> = serde_json::from_str(&json).unwrap();
assert_eq!(
format!("{:.0}", from_json.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\
│ 121 122 123 │\n\
│ 231 232 233 │\n\
│ 131 132 133 │\n\
└ ┘"
);
}
#[test]
fn debug_works() {
let dd = Tensor3::<4, 3>::new();
assert!(!format!("{:?}", dd).is_empty());
}
#[test]
fn constant_permutation_works() {
let perm_a = Tensor3::<9, 3>::constant_permutation();
let expected = [
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, SQRT_2],
[SQRT_2, 0.0, 0.0],
[0.0, -SQRT_2, 0.0],
];
for m in 0..9 {
for n in 0..3 {
approx_eq(perm_a.get(m, n), expected[m][n], 1e-15);
}
}
assert_eq!(
format!("{:.3}", perm_a),
"┌ ┐\n\
│ 0.000 0.000 0.000 │\n\
│ 0.000 0.000 0.000 │\n\
│ 0.000 0.000 0.000 │\n\
│ 0.000 0.000 0.000 │\n\
│ 0.000 0.000 0.000 │\n\
│ 0.000 0.000 0.000 │\n\
│ 0.000 0.000 1.414 │\n\
│ 1.414 0.000 0.000 │\n\
│ 0.000 -1.414 0.000 │\n\
└ ┘"
);
let perm_b = Tensor3::<3, 9>::constant_permutation();
let expected = [
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SQRT_2, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -SQRT_2],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SQRT_2, 0.0, 0.0],
];
for m in 0..3 {
for n in 0..9 {
approx_eq(perm_b.get(m, n), expected[m][n], 1e-15);
}
}
}
}