use crate::{EquationHandler, EssentialBcs1d, Grid1d, NaturalBcs1d, Side, StrError};
use russell_lab::{InterpLagrange, Vector};
use russell_sparse::{CooMatrix, Genie, LinSolver, Sym};
pub struct Spc1d<'a> {
xmin: f64,
xmax: f64,
grid: Grid1d,
ebcs: EssentialBcs1d<'a>,
nbcs: NaturalBcs1d<'a>,
mkx: f64,
equations: EquationHandler,
interp: InterpLagrange,
genie: Genie,
}
impl<'a> Spc1d<'a> {
pub fn new(
xmin: f64,
xmax: f64,
nx: usize,
ebcs: EssentialBcs1d<'a>,
nbcs: NaturalBcs1d<'a>,
kx: f64,
) -> Result<Self, StrError> {
if nx < 2 {
return Err("nx must be ≥ 2");
}
let nn = nx - 1;
if nn > 2048 {
return Err("the maximum allowed polynomial degree is 2048");
}
let grid = Grid1d::new_chebyshev_gauss_lobatto(nx).unwrap();
if ebcs.periodic_along_x {
return Err("essential BCs cannot be periodic");
}
let neq = grid.nx();
let mut equations = EquationHandler::new(neq);
equations.recompute(&ebcs.get_nodes(&grid));
let mut interp_x = InterpLagrange::new(nn, None).unwrap();
interp_x.calc_dd1_matrix();
interp_x.calc_dd2_matrix();
Ok(Spc1d {
xmin,
xmax,
grid,
ebcs,
nbcs,
mkx: -kx,
equations,
interp: interp_x,
genie: Genie::Umfpack,
})
}
pub fn set_solver_options(&mut self, genie: Genie) {
self.genie = genie;
}
pub fn solve_sps<F>(&self, alpha: f64, source: F) -> Result<Vector, StrError>
where
F: Fn(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>(&self, alpha: f64, source: F) -> Result<Vector, StrError>
where
F: Fn(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(&self, a: &Vector) -> Result<Vec<f64>, StrError> {
let neq = self.equations.neq();
if a.dim() != neq {
return Err("a.dim() must equal the number of equations");
}
let d1r = self.interp.get_dd1().unwrap();
let dr_dx = 2.0 / (self.xmax - self.xmin);
let mut wwx = vec![0.0; neq];
for m in 0..neq {
let mut wx = 0.0;
for n in 0..neq {
wx += self.mkx * d1r.get(m, n) * dr_dx * a[n];
}
wwx[m] = wx;
}
Ok(wwx)
}
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_matrices_sps(&self, alpha: f64, extra_nnz: usize) -> (CooMatrix, CooMatrix) {
let nu = self.equations.nu();
let np = self.equations.np();
let nx = self.grid.nx();
let nnz_wcs = nx * nx; 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();
let d1r = self.interp.get_dd1().unwrap();
let d2r = self.interp.get_dd2().unwrap();
let dr_dx = 2.0 / (self.xmax - self.xmin);
let cx = dr_dx * dr_dx;
for &m in self.equations.unknown() {
if self.nbcs.enabled_m(m, &self.grid) {
for n in 0..nx {
let mut val = 0.0;
if m == 0 {
val += -self.mkx * d1r.get(m, n) * dr_dx; }
if m == nx - 1 {
val += self.mkx * d1r.get(m, n) * dr_dx;
}
self.put_val(&mut kk_bar, &mut kk_check, m, n, val);
}
} else {
for n in 0..nx {
let mut val = self.mkx * d2r.get(m, n) * cx;
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(
&self,
alpha: f64,
extra_nnz: usize,
get_constraints_mat: bool,
) -> (CooMatrix, Option<CooMatrix>) {
let (neq, nlag, ndim) = self.get_dims_lmm();
let nx = self.grid.nx();
let nnz_wcs = nx * nx; let mut mm = CooMatrix::new(ndim, ndim, nnz_wcs + extra_nnz + 2 * nlag, Sym::No).unwrap();
let d1r = self.interp.get_dd1().unwrap();
let d2r = self.interp.get_dd2().unwrap();
let dr_dx = 2.0 / (self.xmax - self.xmin);
let cx = dr_dx * dr_dx;
for m in 0..neq {
if self.nbcs.enabled_m(m, &self.grid) {
for n in 0..nx {
let mut val = 0.0;
if m == 0 {
val += -self.mkx * d1r.get(m, n) * dr_dx; }
if m == nx - 1 {
val += self.mkx * d1r.get(m, n) * dr_dx;
}
mm.put(m, n, val).unwrap();
}
} else {
for n in 0..nx {
let mut val = self.mkx * d2r.get(m, n) * cx;
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>(&self, source: F) -> (Vector, Vector, Vector)
where
F: Fn(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 = self.grid.coord(m);
let x = self.map_coord(r);
if self.grid.on_boundary(m) {
if self.grid.is_xmin(m) {
let wn = self.nbcs.functions[0](x);
f_bar[iu] += wn;
}
if self.grid.is_xmax(m) {
let wn = self.nbcs.functions[1](x);
f_bar[iu] += wn;
}
} else {
f_bar[iu] = source(x);
}
});
for index in 0..2 {
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 = self.grid.coord(m);
let x = self.map_coord(r);
let val = self.ebcs.functions[index](x);
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>(&self, source: F) -> (Vector, Vector)
where
F: Fn(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| {
let x = self.map_coord(r);
if self.grid.on_boundary(m) {
if self.grid.is_xmin(m) {
let wn = self.nbcs.functions[0](x);
ff[m] += wn;
}
if self.grid.is_xmax(m) {
let wn = self.nbcs.functions[1](x);
ff[m] += wn;
}
} else {
ff[m] = source(x);
}
});
for index in 0..2 {
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 = self.grid.coord(m);
let x = self.map_coord(r);
let val = self.ebcs.functions[index](x);
ff[neq + ip] = val;
}
}
}
(aa, ff)
}
pub fn for_each_coord<F>(&self, mut callback: F)
where
F: FnMut(usize, f64),
{
self.grid.for_each_coord(|m, r| {
let x = self.map_coord(r);
callback(m, x);
});
}
fn put_val(&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 map_coord(&self, r: f64) -> f64 {
(self.xmax + self.xmin + (self.xmax - self.xmin) * r) / 2.0
}
}
#[cfg(test)]
mod tests {
use super::Spc1d;
use crate::{EssentialBcs1d, NaturalBcs1d, Side};
use russell_lab::Vector;
use russell_sparse::Sym;
#[test]
fn new_captures_errors() {
let ebcs = EssentialBcs1d::new();
let nbcs = NaturalBcs1d::new();
assert_eq!(Spc1d::new(0.0, 1.0, 1, ebcs, nbcs, 1.0).err(), Some("nx must be ≥ 2"));
let mut ebcs = EssentialBcs1d::new();
let mut nbcs = NaturalBcs1d::new();
ebcs.set(Side::Xmin, |_| 0.0);
nbcs.set(Side::Xmax, |_| 0.0);
assert_eq!(
Spc1d::new(0.0, 1.0, 2050, ebcs, nbcs, 1.0).err(),
Some("the maximum allowed polynomial degree is 2048")
);
let mut ebcs = EssentialBcs1d::new();
let nbcs = NaturalBcs1d::new();
ebcs.set_periodic(true);
assert_eq!(
Spc1d::new(0.0, 1.0, 3, ebcs, nbcs, 1.0).err(),
Some("essential BCs cannot be periodic")
);
}
#[test]
fn calculate_flow_vectors_captures_errors() {
let mut ebcs = EssentialBcs1d::new();
let mut nbcs = NaturalBcs1d::new();
ebcs.set(Side::Xmin, |_| 0.0);
nbcs.set(Side::Xmax, |_| 0.0);
let spc = Spc1d::new(0.0, 1.0, 2, ebcs, nbcs, 1.0).unwrap();
let a = Vector::from(&[0.0]); assert_eq!(
spc.calculate_flow_vectors(&a.into()).err(),
Some("a.dim() must equal the number of equations")
);
}
#[test]
fn get_dims_sps_and_get_equations_work() {
let mut ebcs = EssentialBcs1d::new();
let mut nbcs = NaturalBcs1d::new();
ebcs.set(Side::Xmin, |_| 0.0);
nbcs.set(Side::Xmax, |_| 0.0);
let spc = Spc1d::new(0.0, 1.0, 3, ebcs, nbcs, 1.0).unwrap();
assert_eq!(spc.get_dims_sps(), (2, 1));
assert_eq!(spc.get_equations().neq(), 3);
assert_eq!(spc.get_equations().nu(), 2);
assert_eq!(spc.get_equations().np(), 1);
}
#[test]
fn get_matrices_works() {
let mut ebcs = EssentialBcs1d::new();
let nbcs = NaturalBcs1d::new();
ebcs.set_homogeneous();
let spc = Spc1d::new(-1.0, 1.0, 5, ebcs, nbcs, -1.0).unwrap();
let (nu, np) = (3, 2);
assert_eq!(spc.get_dims_sps(), (nu, np));
assert_eq!(spc.get_equations().nu(), nu);
assert_eq!(spc.get_equations().np(), np);
let (kk_bar, kk_check) = spc.get_matrices_sps(0.0, 0);
assert_eq!(kk_bar.get_info(), (nu, nu, 9, Sym::No));
assert_eq!(kk_check.get_info(), (nu, np, 6, Sym::No));
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));
}
}