use crate::util::delta;
use crate::{EquationHandler, EssentialBcs2d, Grid2d, Metrics, NaturalBcs2d, Side, StrError, Transfinite2d};
use russell_lab::{vec_copy_scaled, vec_inner, vec_norm, InterpLagrange, Norm, Vector};
use russell_sparse::{CooMatrix, Genie, LinSolver, Sym};
pub struct SpcMap2d<'a> {
grid: Grid2d,
ebcs: EssentialBcs2d<'a>,
nbcs: NaturalBcs2d<'a>,
mk: f64,
equations: EquationHandler,
interp_r: InterpLagrange,
interp_s: InterpLagrange,
genie: Genie,
metrics: Metrics,
map: Transfinite2d,
x: Vector,
dx_dr: Vector,
dx_ds: Vector,
d2x_dr2: Vector,
d2x_ds2: Vector,
d2x_drs: Vector,
un: Vector,
}
impl<'a> SpcMap2d<'a> {
pub fn new(
map: Transfinite2d,
nr: usize,
ns: usize,
ebcs: EssentialBcs2d<'a>,
nbcs: NaturalBcs2d<'a>,
k: f64,
) -> Result<Self, StrError> {
if nr < 2 {
return Err("nr must be ≥ 2");
}
if ns < 2 {
return Err("ns must be ≥ 2");
}
let nn_r = nr - 1;
let nn_s = ns - 1;
if nn_r > 2048 || nn_s > 2048 {
return Err("the maximum allowed polynomial degree is 2048");
}
let grid = Grid2d::new_chebyshev_gauss_lobatto(nr, ns).unwrap();
if ebcs.periodic_along_x || ebcs.periodic_along_y {
return Err("essential BCs cannot be periodic");
}
let neq = grid.size();
let mut equations = EquationHandler::new(neq);
equations.recompute(&ebcs.get_nodes(&grid));
let mut interp_r = InterpLagrange::new(nn_r, None).unwrap();
let mut interp_s = InterpLagrange::new(nn_s, None).unwrap();
interp_r.calc_dd1_matrix();
interp_s.calc_dd1_matrix();
interp_r.calc_dd2_matrix();
interp_s.calc_dd2_matrix();
let metrics = Metrics::new(2, false);
Ok(SpcMap2d {
grid,
ebcs,
nbcs,
mk: -k,
equations,
interp_r,
interp_s,
genie: Genie::Umfpack,
metrics,
map,
x: Vector::new(2),
dx_dr: Vector::new(2),
dx_ds: Vector::new(2),
d2x_dr2: Vector::new(2),
d2x_ds2: Vector::new(2),
d2x_drs: Vector::new(2),
un: Vector::new(2),
})
}
pub fn set_solver_options(&mut self, genie: Genie) {
self.genie = genie;
}
pub fn solve_sps<F>(&mut self, alpha: f64, source: F) -> Result<Vector, StrError>
where
F: Fn(f64, f64) -> f64,
{
self.ebcs.validate(&self.nbcs)?;
let (kk_bar, kk_check) = self.get_matrices_sps(alpha, 0);
let (mut a_bar, a_check, mut f_bar) = self.get_vectors_sps(source);
kk_check.mat_vec_mul_update(&mut f_bar, -1.0, &a_check).unwrap();
let mut solver = LinSolver::new(self.genie)?;
solver.actual.factorize(&kk_bar, None)?;
solver.actual.solve(&mut a_bar, &f_bar, false)?;
Ok(self.get_joined_vector_sps(&a_bar, &a_check))
}
pub fn solve_lmm<F>(&mut self, alpha: f64, source: F) -> Result<Vector, StrError>
where
F: Fn(f64, f64) -> f64,
{
self.ebcs.validate(&self.nbcs)?;
let (mm, _) = self.get_matrices_lmm(alpha, 0, false);
let (mut aa, ff) = self.get_vectors_lmm(source);
let mut solver = LinSolver::new(self.genie)?;
solver.actual.factorize(&mm, None)?;
solver.actual.solve(&mut aa, &ff, false)?;
let neq = self.equations.neq();
Ok(Vector::from(&&aa.as_data()[..neq]))
}
pub fn calculate_flow_vectors(&mut self, a: &Vector) -> Result<(Vec<f64>, Vec<f64>), StrError> {
let neq = self.equations.neq();
if a.dim() != neq {
return Err("a.dim() must equal the number of equations");
}
let mut wwx = vec![0.0; neq];
let mut wwy = vec![0.0; neq];
let mut w = Vector::new(2);
for m in 0..neq {
self.calculate_metrics(m);
let (i, j) = self.grid.get_ij(m);
w.fill(0.0);
for n in 0..neq {
let (k, l) = self.grid.get_ij(n);
let akl = a[n];
for d in 0..2 {
w[d] += self.mk
* (self.d1r(i, k) * delta(j, l) * self.metrics.g_ctr[0][d]
+ delta(i, k) * self.d1s(j, l) * self.metrics.g_ctr[1][d])
* akl
}
}
wwx[m] = w[0];
wwy[m] = w[1];
}
Ok((wwx, wwy))
}
pub fn get_dims_sps(&self) -> (usize, usize) {
let nu = self.equations.nu();
let np = self.equations.np();
(nu, np)
}
pub fn get_dims_lmm(&self) -> (usize, usize, usize) {
let neq = self.equations.neq();
let nlag = self.equations.np();
let ndim = neq + nlag;
(neq, nlag, ndim)
}
pub fn get_equations(&self) -> &EquationHandler {
&self.equations
}
pub fn get_map(&mut self) -> &mut Transfinite2d {
&mut self.map
}
pub fn get_matrices_sps(&mut self, alpha: f64, extra_nnz: usize) -> (CooMatrix, CooMatrix) {
let nu = self.equations.nu();
let np = self.equations.np();
let nr = self.grid.nx();
let ns = self.grid.ny();
let neq = self.equations.neq();
let nnz_wcs = nr * nr * ns * ns; let mut kk_bar = CooMatrix::new(nu, nu, nnz_wcs + extra_nnz, Sym::No).unwrap();
let mut kk_check = CooMatrix::new(nu, np, nnz_wcs, Sym::No).unwrap();
for m in self.equations.unknown().clone() {
let (i, j) = self.grid.get_ij(m);
self.calculate_metrics(m);
let g11 = self.metrics.gg_mat.get(0, 0);
let g22 = self.metrics.gg_mat.get(1, 1);
let g12 = self.metrics.gg_mat.get(0, 1);
let ll1 = self.metrics.ell_coefficient_for_laplacian(0);
let ll2 = self.metrics.ell_coefficient_for_laplacian(1);
if self.nbcs.enabled_ij(i, j, &self.grid) {
for n in 0..neq {
let (k, l) = self.grid.get_ij(n);
let mut val = 0.0;
if i == 0 {
if j == l {
self.calc_unit_normal(Side::Xmin);
let alpha = vec_inner(&self.un, &self.metrics.g_ctr[0]);
val += self.mk * self.d1r(i, k) * alpha;
}
}
if i == nr - 1 {
if j == l {
self.calc_unit_normal(Side::Xmax);
let alpha = vec_inner(&self.un, &self.metrics.g_ctr[0]);
val += self.mk * self.d1r(i, k) * alpha;
}
}
if j == 0 {
if i == k {
self.calc_unit_normal(Side::Ymin);
let beta = vec_inner(&self.un, &self.metrics.g_ctr[1]);
val += self.mk * self.d1s(j, l) * beta;
}
}
if j == ns - 1 {
if i == k {
self.calc_unit_normal(Side::Ymax);
let beta = vec_inner(&self.un, &self.metrics.g_ctr[1]);
val += self.mk * self.d1s(j, l) * beta;
}
}
self.put_val(&mut kk_bar, &mut kk_check, m, n, val);
}
} else {
for n in 0..neq {
let (k, l) = self.grid.get_ij(n);
let mut val = 0.0
+ self.d2r(i, k) * delta(j, l) * g11
+ delta(i, k) * self.d2s(j, l) * g22
+ self.d1r(i, k) * self.d1s(j, l) * 2.0 * g12
- self.d1r(i, k) * delta(j, l) * ll1
- delta(i, k) * self.d1s(j, l) * ll2;
val *= self.mk;
if m == n {
val += alpha; }
self.put_val(&mut kk_bar, &mut kk_check, m, n, val);
}
}
}
(kk_bar, kk_check)
}
pub fn get_matrices_lmm(
&mut self,
alpha: f64,
extra_nnz: usize,
get_constraints_mat: bool,
) -> (CooMatrix, Option<CooMatrix>) {
let (neq, nlag, ndim) = self.get_dims_lmm();
let nr = self.grid.nx();
let ns = self.grid.ny();
let nnz_wcs = nr * nr * ns * ns; let mut mm = CooMatrix::new(ndim, ndim, nnz_wcs + extra_nnz + 2 * nlag, Sym::No).unwrap();
for m in 0..neq {
let (i, j) = self.grid.get_ij(m);
self.calculate_metrics(m);
let g11 = self.metrics.gg_mat.get(0, 0);
let g22 = self.metrics.gg_mat.get(1, 1);
let g12 = self.metrics.gg_mat.get(0, 1);
let ll1 = self.metrics.ell_coefficient_for_laplacian(0);
let ll2 = self.metrics.ell_coefficient_for_laplacian(1);
if self.nbcs.enabled_ij(i, j, &self.grid) {
for n in 0..neq {
let (k, l) = self.grid.get_ij(n);
let mut val = 0.0;
if i == 0 {
if j == l {
self.calc_unit_normal(Side::Xmin);
let alpha = vec_inner(&self.un, &self.metrics.g_ctr[0]);
val += self.mk * self.d1r(i, k) * alpha;
}
}
if i == nr - 1 {
if j == l {
self.calc_unit_normal(Side::Xmax);
let alpha = vec_inner(&self.un, &self.metrics.g_ctr[0]);
val += self.mk * self.d1r(i, k) * alpha;
}
}
if j == 0 {
if i == k {
self.calc_unit_normal(Side::Ymin);
let beta = vec_inner(&self.un, &self.metrics.g_ctr[1]);
val += self.mk * self.d1s(j, l) * beta;
}
}
if j == ns - 1 {
if i == k {
self.calc_unit_normal(Side::Ymax);
let beta = vec_inner(&self.un, &self.metrics.g_ctr[1]);
val += self.mk * self.d1s(j, l) * beta;
}
}
mm.put(m, n, val).unwrap();
}
} else {
for n in 0..neq {
let (k, l) = self.grid.get_ij(n);
let mut val = 0.0
+ self.d2r(i, k) * delta(j, l) * g11
+ delta(i, k) * self.d2s(j, l) * g22
+ self.d1r(i, k) * self.d1s(j, l) * 2.0 * g12
- self.d1r(i, k) * delta(j, l) * ll1
- delta(i, k) * self.d1s(j, l) * ll2;
val *= self.mk;
if m == n {
val += alpha; }
mm.put(m, n, val).unwrap();
}
}
}
self.equations.prescribed().iter().for_each(|&m| {
let ip = self.equations.ip(m);
mm.put(neq + ip, m, 1.0).unwrap(); mm.put(m, neq + ip, 1.0).unwrap(); });
if get_constraints_mat && nlag > 0 {
let mut cc = CooMatrix::new(nlag, neq, nlag, Sym::No).unwrap();
self.equations.prescribed().iter().for_each(|&m| {
let ip = self.equations.ip(m);
cc.put(ip, m, 1.0).unwrap(); });
(mm, Some(cc))
} else {
(mm, None)
}
}
pub fn get_vectors_sps<F>(&mut self, source: F) -> (Vector, Vector, Vector)
where
F: Fn(f64, f64) -> f64,
{
let nu = self.equations.nu();
let np = self.equations.np();
let a_bar = Vector::new(nu);
let mut a_check = Vector::new(np);
let mut f_bar = Vector::new(nu);
self.equations.unknown().iter().for_each(|&m| {
let iu = self.equations.iu(m);
let (r, s) = self.grid.coord(m);
self.map.point(&mut self.x, r, s);
if self.grid.on_boundary(m) {
if self.grid.is_xmin(m) {
let wn = self.nbcs.functions[0](self.x[0], self.x[1]);
f_bar[iu] += wn;
}
if self.grid.is_xmax(m) {
let wn = self.nbcs.functions[1](self.x[0], self.x[1]);
f_bar[iu] += wn;
}
if self.grid.is_ymin(m) {
let wn = self.nbcs.functions[2](self.x[0], self.x[1]);
f_bar[iu] += wn;
}
if self.grid.is_ymax(m) {
let wn = self.nbcs.functions[3](self.x[0], self.x[1]);
f_bar[iu] += wn;
}
} else {
f_bar[iu] = source(self.x[0], self.x[1]);
}
});
for index in 0..4 {
if self.ebcs.sides[index] {
for &m in self.grid.get_nodes_on_side(Side::from_index(index)) {
let ip = self.equations.ip(m);
let (r, s) = self.grid.coord(m);
self.map.point(&mut self.x, r, s);
let val = self.ebcs.functions[index](self.x[0], self.x[1]);
a_check[ip] = val;
}
}
}
(a_bar, a_check, f_bar)
}
pub fn get_joined_vector_sps(&self, a_bar: &Vector, a_check: &Vector) -> Vector {
let neq = self.equations.neq();
let mut a = Vector::new(neq);
self.equations.unknown().iter().for_each(|&m| {
let iu = self.equations.iu(m);
a[m] = a_bar[iu];
});
self.equations.prescribed().iter().for_each(|&m| {
let ip = self.equations.ip(m);
a[m] = a_check[ip];
});
a
}
pub fn get_vectors_lmm<F>(&mut self, source: F) -> (Vector, Vector)
where
F: Fn(f64, f64) -> f64,
{
let (neq, _, ndim) = self.get_dims_lmm();
let aa = Vector::new(ndim);
let mut ff = Vector::new(ndim);
self.grid.for_each_coord(|m, r, s| {
self.map.point(&mut self.x, r, s);
if self.grid.on_boundary(m) {
if self.grid.is_xmin(m) {
let wn = self.nbcs.functions[0](self.x[0], self.x[1]);
ff[m] += wn;
}
if self.grid.is_xmax(m) {
let wn = self.nbcs.functions[1](self.x[0], self.x[1]);
ff[m] += wn;
}
if self.grid.is_ymin(m) {
let wn = self.nbcs.functions[2](self.x[0], self.x[1]);
ff[m] += wn;
}
if self.grid.is_ymax(m) {
let wn = self.nbcs.functions[3](self.x[0], self.x[1]);
ff[m] += wn;
}
} else {
ff[m] = source(self.x[0], self.x[1]);
}
});
for index in 0..4 {
if self.ebcs.sides[index] {
for &m in self.grid.get_nodes_on_side(Side::from_index(index)) {
let ip = self.equations.ip(m);
let (r, s) = self.grid.coord(m);
self.map.point(&mut self.x, r, s);
let val = self.ebcs.functions[index](self.x[0], self.x[1]);
ff[neq + ip] = val;
}
}
}
(aa, ff)
}
pub fn for_each_coord<F>(&mut self, mut callback: F)
where
F: FnMut(usize, f64, f64),
{
self.grid.for_each_coord(|m, r, s| {
self.map.point(&mut self.x, r, s);
callback(m, self.x[0], self.x[1]);
});
}
fn calc_unit_normal(&mut self, side: Side) {
match side {
Side::Xmin => vec_copy_scaled(&mut self.un, -1.0, &self.metrics.g_ctr[0]).unwrap(),
Side::Xmax => vec_copy_scaled(&mut self.un, 1.0, &self.metrics.g_ctr[0]).unwrap(),
Side::Ymin => vec_copy_scaled(&mut self.un, -1.0, &self.metrics.g_ctr[1]).unwrap(),
Side::Ymax => vec_copy_scaled(&mut self.un, 1.0, &self.metrics.g_ctr[1]).unwrap(),
}
let norm_u = vec_norm(&mut self.un, Norm::Euc);
self.un[0] /= norm_u;
self.un[1] /= norm_u;
}
fn put_val(&mut self, kk_bar: &mut CooMatrix, kk_check: &mut CooMatrix, m: usize, n: usize, val: f64) {
let row = self.equations.iu(m);
if !self.equations.is_prescribed(n) {
let col = self.equations.iu(n);
kk_bar.put(row, col, val).unwrap();
} else {
let col = self.equations.ip(n);
kk_check.put(row, col, val).unwrap();
}
}
fn calculate_metrics(&mut self, m: usize) {
let (r, s) = self.grid.coord(m);
self.map.point_and_derivs(
&mut self.x,
&mut self.dx_dr,
&mut self.dx_ds,
Some(&mut self.d2x_dr2),
Some(&mut self.d2x_ds2),
Some(&mut self.d2x_drs),
r,
s,
);
self.metrics
.calculate_2d(
&self.dx_dr,
&self.dx_ds,
Some(&self.d2x_dr2),
Some(&self.d2x_ds2),
Some(&self.d2x_drs),
)
.unwrap();
}
#[inline]
fn d1r(&self, i: usize, j: usize) -> f64 {
self.interp_r.get_dd1().unwrap().get(i, j)
}
#[inline]
fn d1s(&self, i: usize, j: usize) -> f64 {
self.interp_s.get_dd1().unwrap().get(i, j)
}
#[inline]
fn d2r(&self, i: usize, j: usize) -> f64 {
self.interp_r.get_dd2().unwrap().get(i, j)
}
#[inline]
fn d2s(&self, i: usize, j: usize) -> f64 {
self.interp_s.get_dd2().unwrap().get(i, j)
}
}
#[cfg(test)]
mod tests {
use super::SpcMap2d;
use crate::{EssentialBcs2d, NaturalBcs2d, Side, TransfiniteSamples};
use russell_lab::{mat_approx_eq, Vector};
use russell_sparse::Sym;
#[test]
fn new_captures_errors() {
let map = TransfiniteSamples::quadrilateral_2d(&[-1.0, -1.0], &[1.0, -1.0], &[1.0, 1.0], &[-1.0, 1.0]);
let ebcs = EssentialBcs2d::new();
let nbcs = NaturalBcs2d::new();
assert_eq!(SpcMap2d::new(map, 1, 2, ebcs, nbcs, 1.0).err(), Some("nr must be ≥ 2"));
let map = TransfiniteSamples::quadrilateral_2d(&[-1.0, -1.0], &[1.0, -1.0], &[1.0, 1.0], &[-1.0, 1.0]);
let ebcs = EssentialBcs2d::new();
let nbcs = NaturalBcs2d::new();
assert_eq!(SpcMap2d::new(map, 2, 1, ebcs, nbcs, 1.0).err(), Some("ns must be ≥ 2"));
let map = TransfiniteSamples::quadrilateral_2d(&[-1.0, -1.0], &[1.0, -1.0], &[1.0, 1.0], &[-1.0, 1.0]);
let mut ebcs = EssentialBcs2d::new();
let mut nbcs = NaturalBcs2d::new();
ebcs.set(Side::Xmin, |_, _| 0.0);
nbcs.set(Side::Xmax, |_, _| 0.0);
ebcs.set(Side::Ymin, |_, _| 0.0);
nbcs.set(Side::Ymax, |_, _| 0.0);
assert_eq!(
SpcMap2d::new(map, 2050, 2, ebcs, nbcs, 1.0).err(),
Some("the maximum allowed polynomial degree is 2048")
);
let map = TransfiniteSamples::quadrilateral_2d(&[-1.0, -1.0], &[1.0, -1.0], &[1.0, 1.0], &[-1.0, 1.0]);
let mut ebcs = EssentialBcs2d::new();
let mut nbcs = NaturalBcs2d::new();
ebcs.set(Side::Xmin, |_, _| 0.0);
nbcs.set(Side::Xmax, |_, _| 0.0);
ebcs.set(Side::Ymin, |_, _| 0.0);
nbcs.set(Side::Ymax, |_, _| 0.0);
assert_eq!(
SpcMap2d::new(map, 2, 2050, ebcs, nbcs, 1.0).err(),
Some("the maximum allowed polynomial degree is 2048")
);
let map = TransfiniteSamples::quadrilateral_2d(&[-1.0, -1.0], &[1.0, -1.0], &[1.0, 1.0], &[-1.0, 1.0]);
let mut ebcs = EssentialBcs2d::new();
let nbcs = NaturalBcs2d::new();
ebcs.set_periodic(true, true);
assert_eq!(
SpcMap2d::new(map, 3, 3, ebcs, nbcs, 1.0).err(),
Some("essential BCs cannot be periodic")
);
}
#[test]
fn calculate_flow_vectors_captures_errors() {
let map = TransfiniteSamples::quadrilateral_2d(&[-1.0, -1.0], &[1.0, -1.0], &[1.0, 1.0], &[-1.0, 1.0]);
let mut ebcs = EssentialBcs2d::new();
ebcs.set_homogeneous();
let nbcs = NaturalBcs2d::new();
let mut spc = SpcMap2d::new(map, 2, 2, ebcs, nbcs, 1.0).unwrap();
let a = Vector::from(&[0.0]); assert_eq!(
spc.calculate_flow_vectors(&a).err(),
Some("a.dim() must equal the number of equations")
);
}
#[test]
fn get_matrices_works_1() {
let map = TransfiniteSamples::quadrilateral_2d(&[-1.0, -1.0], &[1.0, -1.0], &[1.0, 1.0], &[-1.0, 1.0]);
let mut ebcs = EssentialBcs2d::new();
let nbcs = NaturalBcs2d::new();
ebcs.set_homogeneous();
let (nr, ns) = (5, 5);
let mut spc = SpcMap2d::new(map, nr, ns, ebcs, nbcs, 1.0).unwrap();
let (kk_bar, kk_check) = spc.get_matrices_sps(0.0, 0);
let kk_bar_dense = kk_bar.as_dense();
let (nu, np) = (9, 16);
assert_eq!(spc.get_dims_sps(), (nu, np));
assert_eq!(spc.get_equations().nu(), nu);
assert_eq!(spc.get_equations().np(), np);
let ___ = 0.0;
#[rustfmt::skip]
let correct_kk_bar = &[
[ 28.0, -6.0, 2.0, -6.0, ___, ___, 2.0, ___, ___],
[ -4.0, 20.0, -4.0, ___, -6.0, ___, ___, 2.0, ___],
[ 2.0, -6.0, 28.0, ___, ___, -6.0, ___, ___, 2.0],
[ -4.0, ___, ___, 20.0, -6.0, 2.0, -4.0, ___, ___],
[ ___, -4.0, ___, -4.0, 12.0, -4.0, ___, -4.0, ___],
[ ___, ___, -4.0, 2.0, -6.0, 20.0, ___, ___, -4.0],
[ 2.0, ___, ___, -6.0, ___, ___, 28.0, -6.0, 2.0],
[ ___, 2.0, ___, ___, -6.0, ___, -4.0, 20.0, -4.0],
[ ___, ___, 2.0, ___, ___, -6.0, 2.0, -6.0, 28.0],
];
mat_approx_eq(&kk_bar_dense, correct_kk_bar, 1e-13);
#[rustfmt::skip]
let correct_kk_check = &[
[0.0, -9.242640687119286, 0.0, 0.0, 0.0, -9.242640687119286, -0.7573593128807143, 0.0, 0.0, 0.0, 0.0, 0.0, -0.7573593128807143, 0.0, 0.0, 0.0],
[0.0, 0.0, -9.242640687119286, 0.0, 0.0, 0.9999999999999998, 0.9999999999999993, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.7573593128807143, 0.0, 0.0],
[0.0, 0.0, 0.0, -9.242640687119286, 0.0, -0.757359312880714, -9.242640687119286, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.7573593128807143, 0.0],
[0.0, 0.9999999999999998, 0.0, 0.0, 0.0, 0.0, 0.0, -9.242640687119286, -0.7573593128807143, 0.0, 0.0, 0.0, 0.9999999999999993, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.9999999999999998, 0.0, 0.0, 0.0, 0.0, 0.9999999999999998, 0.9999999999999993, 0.0, 0.0, 0.0, 0.0, 0.9999999999999993, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.9999999999999998, 0.0, 0.0, 0.0, -0.757359312880714, -9.242640687119286, 0.0, 0.0, 0.0, 0.0, 0.0, 0.9999999999999993, 0.0],
[0.0, -0.757359312880714, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -9.242640687119286, -0.7573593128807143, 0.0, -9.242640687119286, 0.0, 0.0, 0.0],
[0.0, 0.0, -0.757359312880714, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.9999999999999998, 0.9999999999999993, 0.0, 0.0, -9.242640687119286, 0.0, 0.0],
[0.0, 0.0, 0.0, -0.757359312880714, 0.0, 0.0, 0.0, 0.0, 0.0, -0.757359312880714, -9.242640687119286, 0.0, 0.0, 0.0, -9.242640687119286, 0.0],
];
let kk_check_dense = kk_check.as_dense();
mat_approx_eq(&kk_check_dense, correct_kk_check, 1e-14);
let neq = nu + np;
let nlag = np;
let ndim = neq + nlag;
assert_eq!(spc.get_dims_lmm(), (neq, nlag, ndim));
let nnz = neq * neq + 2 * nlag;
let (mm, cc) = spc.get_matrices_lmm(0.0, 0, true);
assert_eq!(mm.get_info(), (ndim, ndim, nnz, Sym::No));
let cc = cc.unwrap();
assert_eq!(cc.get_info(), (nlag, neq, nlag, Sym::No));
let _ = spc.get_map();
}
}