use super::CommonArgs;
use crate::StrError;
use russell_lab::math::SQRT_2;
use russell_lab::{Matrix, Vector};
use russell_tensor::{Mandel, Tensor2};
pub fn vec_04_bt<F>(d: &mut Vector, args: &mut CommonArgs, mut fn_sig: F) -> Result<(), StrError>
where
F: FnMut(&mut Tensor2, usize, &Vector, &Matrix) -> Result<(), StrError>,
{
let (space_ndim, nnode) = args.pad.xxt.dims();
if d.dim() < args.ii0 + nnode * space_ndim {
return Err("d.len() must be ≥ ii0 + nnode ⋅ space_ndim");
}
if args.axisymmetric && space_ndim != 2 {
return Err("axisymmetric requires space_ndim = 2");
}
let mut sig = Tensor2::new(Mandel::new(2 * space_ndim));
if args.clear {
d.fill(0.0);
}
for p in 0..args.gauss.npoint() {
let iota = args.gauss.coords(p);
let weight = args.gauss.weight(p);
(args.pad.fn_interp)(&mut args.pad.interp, iota); let det_jac = args.pad.calc_gradient(iota)?;
let nn = &args.pad.interp;
let bb = &args.pad.gradient;
fn_sig(&mut sig, p, nn, bb)?;
let c = det_jac * weight * args.alpha;
if args.axisymmetric {
let mut r = 0.0; for m in 0..nnode {
r += nn[m] * args.pad.xxt.get(0, m);
}
add_to_d_axisymmetric(d, nnode, c, r, &sig, args);
} else {
add_to_d(d, space_ndim, nnode, c, &sig, args);
}
}
Ok(())
}
#[inline]
fn add_to_d(d: &mut Vector, ndim: usize, nnode: usize, c: f64, sig: &Tensor2, args: &mut CommonArgs) {
let t = sig.vector();
let s = SQRT_2;
let b = &args.pad.gradient;
let ii0 = args.ii0;
if ndim == 2 {
for m in 0..nnode {
d[ii0 + 0 + m * 2] += c * (t[0] * b.get(m, 0) + t[3] * b.get(m, 1) / s);
d[ii0 + 1 + m * 2] += c * (t[3] * b.get(m, 0) / s + t[1] * b.get(m, 1));
}
} else {
for m in 0..nnode {
d[ii0 + 0 + m * 3] += c * (t[0] * b.get(m, 0) + t[3] * b.get(m, 1) / s + t[5] * b.get(m, 2) / s);
d[ii0 + 1 + m * 3] += c * (t[3] * b.get(m, 0) / s + t[1] * b.get(m, 1) + t[4] * b.get(m, 2) / s);
d[ii0 + 2 + m * 3] += c * (t[5] * b.get(m, 0) / s + t[4] * b.get(m, 1) / s + t[2] * b.get(m, 2));
}
}
}
#[inline]
fn add_to_d_axisymmetric(d: &mut Vector, nnode: usize, c: f64, r: f64, sig: &Tensor2, args: &mut CommonArgs) {
let t = sig.vector();
let s = SQRT_2;
let nn = &args.pad.interp;
let b = &args.pad.gradient;
let ii0 = args.ii0;
for m in 0..nnode {
d[ii0 + 0 + m * 2] += c * r * (t[0] * b.get(m, 0) + t[3] * b.get(m, 1) / s) + c * nn[m] * t[2];
d[ii0 + 1 + m * 2] += c * r * (t[3] * b.get(m, 0) / s + t[1] * b.get(m, 1));
}
}
#[cfg(test)]
mod tests {
use crate::integ::testing::aux;
use crate::integ::{self, AnalyticalTet4, AnalyticalTri3, CommonArgs, Gauss};
use russell_lab::{vec_approx_eq, Matrix, Vector};
use russell_tensor::{Mandel, Tensor2};
#[test]
fn capture_some_errors() {
let mut pad = aux::gen_pad_lin2(1.0);
let mut d = Vector::new(4);
let mut sig = Tensor2::new(Mandel::Symmetric2D);
let nn = Vector::new(0);
let bb = Matrix::new(0, 0);
let f = |_: &mut Tensor2, _, _: &Vector, _: &Matrix| Ok(());
f(&mut sig, 0, &nn, &bb).unwrap();
let gauss = Gauss::new(pad.kind);
let mut args = CommonArgs::new(&mut pad, &gauss);
args.ii0 = 1;
assert_eq!(
integ::vec_04_bt(&mut d, &mut args, f).err(),
Some("d.len() must be ≥ ii0 + nnode ⋅ space_ndim")
);
}
#[test]
fn tri3_constant_works() {
let mut pad = aux::gen_pad_tri3();
const S00: f64 = 2.0;
const S11: f64 = 3.0;
const S22: f64 = 4.0;
const S01: f64 = 5.0;
let ana = AnalyticalTri3::new(&pad);
let sig = Tensor2::from_matrix(
&[[S00, S01, 0.0], [S01, S11, 0.0], [0.0, 0.0, S22]],
Mandel::Symmetric2D,
)
.unwrap();
let d_correct = ana.vec_04_bt(&sig, false);
let class = pad.kind.class();
let tolerances = [1e-14, 1e-14, 1e-14, 1e-13, 1e-14];
let selection: Vec<_> = [1, 3, 4, 12, 16]
.iter()
.map(|n| Gauss::new_sized(class, *n).unwrap())
.collect();
let (space_ndim, nnode) = pad.xxt.dims();
let mut d = Vector::filled(nnode * space_ndim, aux::NOISE);
selection.iter().zip(tolerances).for_each(|(ips, tol)| {
let mut args = CommonArgs::new(&mut pad, ips);
integ::vec_04_bt(&mut d, &mut args, |sig, _, _, _| {
sig.sym_set(0, 0, S00);
sig.sym_set(1, 1, S11);
sig.sym_set(2, 2, S22);
sig.sym_set(0, 1, S01);
Ok(())
})
.unwrap();
vec_approx_eq(&d, &d_correct, tol);
});
}
#[test]
fn tri3_constant_axisymmetric_works() {
let mut pad = aux::gen_pad_tri3();
const S00: f64 = 2.0;
const S11: f64 = 3.0;
const S22: f64 = 4.0;
const S01: f64 = 5.0;
let ana = AnalyticalTri3::new(&pad);
let sig = Tensor2::from_matrix(
&[[S00, S01, 0.0], [S01, S11, 0.0], [0.0, 0.0, S22]],
Mandel::Symmetric2D,
)
.unwrap();
let d_correct = ana.vec_04_bt(&sig, true);
let class = pad.kind.class();
let tolerances = [1e-13, 1e-13];
let selection: Vec<_> = [1, 3].iter().map(|n| Gauss::new_sized(class, *n).unwrap()).collect();
let (space_ndim, nnode) = pad.xxt.dims();
let mut d = Vector::filled(nnode * space_ndim, aux::NOISE);
selection.iter().zip(tolerances).for_each(|(ips, tol)| {
let mut args = CommonArgs::new(&mut pad, ips);
args.axisymmetric = true;
integ::vec_04_bt(&mut d, &mut args, |sig, _, _, _| {
sig.sym_set(0, 0, S00);
sig.sym_set(1, 1, S11);
sig.sym_set(2, 2, S22);
sig.sym_set(0, 1, S01);
Ok(())
})
.unwrap();
vec_approx_eq(&d, &d_correct, tol);
});
}
#[test]
fn tet4_constant_works() {
let mut pad = aux::gen_pad_tet4();
#[rustfmt::skip]
let tt = Tensor2::from_matrix(&[
[2.0, 5.0, 7.0],
[5.0, 3.0, 6.0],
[7.0, 6.0, 4.0],
], Mandel::Symmetric).unwrap();
let ana = AnalyticalTet4::new(&pad);
let d_correct = ana.vec_04_bt(&tt);
let class = pad.kind.class();
let tolerances = [1e-14, 1e-14, 1e-13, 1e-14, 1e-13, 1e-13, 1e-13];
let selection: Vec<_> = [1, 4, 5, 8, 14, 15, 24]
.iter()
.map(|n| Gauss::new_sized(class, *n).unwrap())
.collect();
let (space_ndim, nnode) = pad.xxt.dims();
let mut d = Vector::filled(nnode * space_ndim, aux::NOISE);
selection.iter().zip(tolerances).for_each(|(ips, tol)| {
let mut args = CommonArgs::new(&mut pad, ips);
integ::vec_04_bt(&mut d, &mut args, |sig, _, _, _| {
sig.set_tensor(1.0, &tt);
Ok(())
})
.unwrap();
vec_approx_eq(&d, &d_correct, tol);
});
}
}