use crate::{
find_jacobian_non_zeros, find_matrix_non_zeros, ode_solver::problem::OdeSolverSolution,
ConstantOp, JacobianColoring, LinearOp, Matrix, MatrixHost, MatrixSparsity, NonLinearOp,
NonLinearOpJacobian, OdeEquations, OdeEquationsImplicit, OdeEquationsRef, OdeSolverProblem, Op,
ParameterisedOp, UnitCallable, Vector,
};
use num_traits::{FromPrimitive, One, Zero};
const NPREY: usize = 1;
const NUM_SPECIES: usize = 2 * NPREY;
const AX: f64 = 1.0;
const AY: f64 = 1.0;
const AA: f64 = 1.0;
const EE: f64 = 10000.0;
const GG: f64 = 0.5e-6;
const BB: f64 = 1.0;
const DPREY: f64 = 1.0;
const DPRED: f64 = 0.05;
const ALPHA: f64 = 50.0;
const BETA: f64 = 1000.0;
#[cfg(feature = "diffsl")]
#[allow(clippy::type_complexity)]
pub fn foodweb_diffsl_problem<M, CG, const NX: usize>() -> (
OdeSolverProblem<impl OdeEquationsImplicit<M = M, V = M::V, T = M::T, C = M::C>>,
OdeSolverSolution<M::V>,
)
where
M: MatrixHost<T = f64>,
CG: crate::CodegenModuleJit + crate::CodegenModuleCompile,
{
use crate::{OdeBuilder, VectorHost};
let (problem, _soln) = foodweb_problem::<M, NX>();
let u0 = problem.eqn.init().call(0.0);
let diffop = FoodWebDiff::<M, NX>::new(&u0, 0.0);
let diff = diffop.jacobian(&u0, 0.0);
let diff_diffsl = diff
.triplet_iter()
.map(|(i, j, v)| format!(" ({i}, {j}): {v}"))
.collect::<Vec<_>>()
.join(",\n");
let mut xx = M::V::zeros(NX * NX, problem.context().clone());
let mut yy = M::V::zeros(NX * NX, problem.context().clone());
for jy in 0..NX {
let y = jy as f64 * AY / (NX as f64 - 1.0);
for jx in 0..NX {
let x = jx as f64 * AX / (NX as f64 - 1.0);
let loc = jx + NX * jy;
xx[loc] = x;
yy[loc] = y;
}
}
let xx_diffsl = xx
.as_slice()
.iter()
.map(|v| format!(" {v}"))
.collect::<Vec<_>>()
.join(",\n");
let yy_diffsl = yy
.as_slice()
.iter()
.map(|v| format!(" {v}"))
.collect::<Vec<_>>()
.join(",\n");
let code = format!(
"
AA {{ 1.0 }}
EE {{ 10000.0 }}
GG {{ 0.5e-6 }}
BB {{ 1.0 }}
ALPHA {{ 50.0 }}
BETA {{ 1000.0 }}
PI {{ 3.141592653589793 }}
DPREY {{ 1.0 }}
DPRED {{ 0.05 }}
D_ij {{
{}
}}
xx_i {{
{}
}}
yy_i {{
{}
}}
tl_i {{
(0): 1.0,
(1:{n}): 0.0,
}}
br_i {{
(0:{n1}): 0.0,
({n1}): 1.0,
}}
b_i {{
(1.0 + ALPHA * xx_i * yy_i + BETA * sin(4.0 * PI * xx_i) * sin(4.0 * PI * yy_i))
}}
u_i {{
c1 = 10.0 + pow(16.0 * xx_i * (1.0 - xx_i) * yy_i * (1.0 - yy_i), 2),
({n}:{n2}): c2 = 1.0e5,
}}
dudt_i {{
(0:{n}): dc1dt = 0,
({n}:{n2}): dc2dt = 0,
}}
M_i {{
dc1dt_i,
({n}:{n2}): 0,
}}
c1diff_i {{
DPREY * D_ij * c1_j,
}}
c2diff_i {{
DPRED * D_ij * c2_j,
}}
F_i {{
c1diff_i + c1_i * (BB * b_i - AA * c1_i - GG * c2_i),
c2diff_i + c2_i * (-BB * b_i + EE * c1_i - AA * c2_i),
}}
out_i {{
tl_j * c1_j,
br_j * c1_j,
tl_j * c2_j,
br_j * c2_j,
}}",
diff_diffsl,
xx_diffsl,
yy_diffsl,
n = NX * NX,
n1 = NX * NX - 1,
n2 = 2 * NX * NX,
);
let problem = OdeBuilder::<M>::new()
.rtol(1e-5)
.atol([1e-5])
.build_from_diffsl::<CG>(code.as_str())
.unwrap();
let soln = soln::<M>(problem.context().clone());
(problem, soln)
}
pub struct FoodWebContext<M, const NX: usize>
where
M: MatrixHost,
{
acoef: [[M::T; NUM_SPECIES]; NUM_SPECIES],
bcoef: [M::T; NUM_SPECIES],
cox: [M::T; NUM_SPECIES],
coy: [M::T; NUM_SPECIES],
nstates: usize,
ctx: M::C,
}
impl<M, const NX: usize> FoodWebContext<M, NX>
where
M: MatrixHost,
{
const DX: f64 = AX / (NX as f64 - 1.0);
const DY: f64 = AY / (NX as f64 - 1.0);
pub fn new(ctx: M::C) -> Self {
let mut acoef = [[M::T::zero(); NUM_SPECIES]; NUM_SPECIES];
let mut bcoef = [M::T::zero(); NUM_SPECIES];
let mut cox = [M::T::zero(); NUM_SPECIES];
let mut coy = [M::T::zero(); NUM_SPECIES];
let nstates = NUM_SPECIES * NX * NX;
for i in 0..NPREY {
for j in 0..NPREY {
acoef[i][NPREY + j] = M::T::from_f64(-GG).unwrap();
acoef[i + NPREY][j] = M::T::from_f64(EE).unwrap();
acoef[i][j] = M::T::zero();
acoef[i + NPREY][NPREY + j] = M::T::zero();
}
acoef[i][i] = M::T::from_f64(-AA).unwrap();
acoef[i + NPREY][i + NPREY] = M::T::from_f64(-AA).unwrap();
bcoef[i] = M::T::from_f64(BB).unwrap();
bcoef[i + NPREY] = M::T::from_f64(-BB).unwrap();
cox[i] = M::T::from_f64(DPREY / Self::DX.powi(2)).unwrap();
cox[i + NPREY] = M::T::from_f64(DPRED / Self::DX.powi(2)).unwrap();
coy[i] = M::T::from_f64(DPREY / Self::DY.powi(2)).unwrap();
coy[i + NPREY] = M::T::from_f64(DPRED / Self::DY.powi(2)).unwrap();
}
Self {
acoef,
bcoef,
cox,
coy,
nstates,
ctx,
}
}
}
impl<M, const NX: usize> Default for FoodWebContext<M, NX>
where
M: MatrixHost,
{
fn default() -> Self {
Self::new(M::C::default())
}
}
struct FoodWebInit<'a, M, const NX: usize>
where
M: MatrixHost,
{
pub foodweb: &'a FoodWeb<M, NX>,
}
macro_rules! context_consts {
($name:ident) => {
impl<'a, M, const NX: usize> $name<'a, M, NX>
where
M: MatrixHost,
{
pub fn new(foodweb: &'a FoodWeb<M, NX>) -> Self {
Self { foodweb }
}
}
};
}
macro_rules! impl_op {
($name:ident) => {
impl<'a, M, const NX: usize> Op for $name<'a, M, NX>
where
M: MatrixHost,
{
type M = M;
type V = M::V;
type T = M::T;
type C = M::C;
fn nout(&self) -> usize {
self.foodweb.context.nstates
}
fn nparams(&self) -> usize {
0
}
fn nstates(&self) -> usize {
self.foodweb.context.nstates
}
fn context(&self) -> &Self::C {
self.foodweb.context()
}
}
};
}
context_consts!(FoodWebInit);
impl_op!(FoodWebInit);
impl<M, const NX: usize> ConstantOp for FoodWebInit<'_, M, NX>
where
M: MatrixHost,
{
#[allow(unused_mut)]
fn call_inplace(&self, _t: M::T, mut y: &mut M::V) {
let nsmx: usize = NUM_SPECIES * NX;
let dx: f64 = AX / (NX as f64 - 1.0);
let dy: f64 = AY / (NX as f64 - 1.0);
for jy in 0..NX {
let yy = jy as f64 * dy;
let yloc = nsmx * jy;
for jx in 0..NX {
let xx = jx as f64 * dx;
let xyfactor = 16.0 * xx * (1.0 - xx) * yy * (1.0 - yy);
let xyfactor = xyfactor.powi(2);
let loc = yloc + NUM_SPECIES * jx;
for is in 0..NUM_SPECIES {
if is < NPREY {
y[loc + is] = M::T::from_f64(10.0 + (is + 1) as f64 * xyfactor).unwrap();
} else {
y[loc + is] = M::T::from_f64(1.0e5).unwrap();
}
}
}
}
}
}
struct FoodWebRhs<'a, M, const NX: usize>
where
M: MatrixHost,
{
pub foodweb: &'a FoodWeb<M, NX>,
}
impl<'a, M, const NX: usize> FoodWebRhs<'a, M, NX>
where
M: MatrixHost,
{
pub fn new(foodweb: &'a FoodWeb<M, NX>) -> Self {
Self { foodweb }
}
}
impl<M, const NX: usize> Op for FoodWebRhs<'_, M, NX>
where
M: MatrixHost,
{
type M = M;
type V = M::V;
type T = M::T;
type C = M::C;
fn nout(&self) -> usize {
self.foodweb.context.nstates
}
fn nparams(&self) -> usize {
0
}
fn nstates(&self) -> usize {
self.foodweb.context.nstates
}
fn context(&self) -> &Self::C {
self.foodweb.context()
}
}
impl<M, const NX: usize> NonLinearOp for FoodWebRhs<'_, M, NX>
where
M: MatrixHost,
{
#[allow(unused_mut)]
fn call_inplace(&self, x: &M::V, _t: M::T, mut y: &mut M::V) {
let nsmx: usize = NUM_SPECIES * NX;
let dx: f64 = AX / (NX as f64 - 1.0);
let dy: f64 = AY / (NX as f64 - 1.0);
let mut rates = [M::T::zero(); NUM_SPECIES];
for jy in 0..NX {
let yy = jy as f64 * dy;
let idyu = if jy != NX - 1 {
nsmx as i32
} else {
-(nsmx as i32)
};
let idyl = if jy != 0 { nsmx as i32 } else { -(nsmx as i32) };
for jx in 0..NX {
let xx = jx as f64 * dx;
let idxu = if jx != NX - 1 {
NUM_SPECIES as i32
} else {
-(NUM_SPECIES as i32)
};
let idxl = if jx != 0 {
NUM_SPECIES as i32
} else {
-(NUM_SPECIES as i32)
};
let loc = NUM_SPECIES * jx + nsmx * jy;
let locxu = (loc as i32 + idxu) as usize;
let locxl = (loc as i32 - idxl) as usize;
let locyu = (loc as i32 + idyu) as usize;
let locyl = (loc as i32 - idyl) as usize;
for (is, rate) in rates.iter_mut().enumerate().take(NUM_SPECIES) {
let mut dp = M::T::zero();
for js in 0..NUM_SPECIES {
dp += self.foodweb.context.acoef[is][js] * x[loc + js];
}
*rate = dp;
}
let fac = M::T::from_f64(
1.0 + ALPHA * xx * yy
+ BETA
* (4.0 * std::f64::consts::PI * xx).sin()
* (4.0 * std::f64::consts::PI * yy).sin(),
)
.unwrap();
for is in 0..NUM_SPECIES {
rates[is] = x[loc + is] * (self.foodweb.context.bcoef[is] * fac + rates[is]);
}
for is in 0..NUM_SPECIES {
let dcyli = x[loc + is] - x[locyl + is];
let dcyui = x[locyu + is] - x[loc + is];
let dcxli = x[loc + is] - x[locxl + is];
let dcxui = x[locxu + is] - x[loc + is];
y[loc + is] = self.foodweb.context.coy[is] * (dcyui - dcyli)
+ self.foodweb.context.cox[is] * (dcxui - dcxli)
+ rates[is];
}
}
}
}
}
impl<M, const NX: usize> NonLinearOpJacobian for FoodWebRhs<'_, M, NX>
where
M: MatrixHost,
{
#[allow(unused_mut)]
fn jac_mul_inplace(&self, x: &M::V, _t: M::T, v: &M::V, mut y: &mut M::V) {
let nsmx: usize = NUM_SPECIES * NX;
let dx: f64 = AX / (NX as f64 - 1.0);
let dy: f64 = AY / (NX as f64 - 1.0);
let mut rates = [M::T::zero(); NUM_SPECIES];
let mut drates = [M::T::zero(); NUM_SPECIES];
for jy in 0..NX {
let yy = jy as f64 * dy;
let idyu = if jy != NX - 1 {
nsmx as i32
} else {
-(nsmx as i32)
};
let idyl = if jy != 0 { nsmx as i32 } else { -(nsmx as i32) };
for jx in 0..NX {
let xx = jx as f64 * dx;
let idxu = if jx != NX - 1 {
NUM_SPECIES as i32
} else {
-(NUM_SPECIES as i32)
};
let idxl = if jx != 0 {
NUM_SPECIES as i32
} else {
-(NUM_SPECIES as i32)
};
let loc = NUM_SPECIES * jx + nsmx * jy;
let locxu = (loc as i32 + idxu) as usize;
let locxl = (loc as i32 - idxl) as usize;
let locyu = (loc as i32 + idyu) as usize;
let locyl = (loc as i32 - idyl) as usize;
for is in 0..NUM_SPECIES {
let mut ddp = M::T::zero();
let mut dp = M::T::zero();
for js in 0..NUM_SPECIES {
dp += self.foodweb.context.acoef[is][js] * x[loc + js];
ddp += self.foodweb.context.acoef[is][js] * v[loc + js];
}
rates[is] = dp;
drates[is] = ddp;
}
let fac = M::T::from_f64(
1.0 + ALPHA * xx * yy
+ BETA
* (4.0 * std::f64::consts::PI * xx).sin()
* (4.0 * std::f64::consts::PI * yy).sin(),
)
.unwrap();
for is in 0..NUM_SPECIES {
drates[is] = x[loc + is] * drates[is]
+ v[loc + is] * (self.foodweb.context.bcoef[is] * fac + rates[is]);
}
for is in 0..NUM_SPECIES {
let dcyli = v[loc + is] - v[locyl + is];
let dcyui = v[locyu + is] - v[loc + is];
let dcxli = v[loc + is] - v[locxl + is];
let dcxui = v[locxu + is] - v[loc + is];
y[loc + is] = self.foodweb.context.coy[is] * (dcyui - dcyli)
+ self.foodweb.context.cox[is] * (dcxui - dcxli)
+ drates[is];
}
}
}
}
fn jacobian_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
if let Some(coloring) = self.foodweb.rhs_coloring.as_ref() {
coloring.jacobian_inplace(self, x, t, y);
} else {
self._default_jacobian_inplace(x, t, y);
}
}
fn jacobian_sparsity(&self) -> Option<M::Sparsity> {
self.foodweb.rhs_sparsity.clone()
}
}
struct FoodWebMass<'a, M, const NX: usize>
where
M: MatrixHost,
{
pub foodweb: &'a FoodWeb<M, NX>,
}
impl<'a, M, const NX: usize> FoodWebMass<'a, M, NX>
where
M: MatrixHost,
{
pub fn new(foodweb: &'a FoodWeb<M, NX>) -> Self {
Self { foodweb }
}
}
impl<M, const NX: usize> Op for FoodWebMass<'_, M, NX>
where
M: MatrixHost,
{
type M = M;
type V = M::V;
type T = M::T;
type C = M::C;
fn nout(&self) -> usize {
self.foodweb.context.nstates
}
fn nparams(&self) -> usize {
0
}
fn nstates(&self) -> usize {
self.foodweb.context.nstates
}
fn context(&self) -> &Self::C {
self.foodweb.context()
}
}
impl<M, const NX: usize> LinearOp for FoodWebMass<'_, M, NX>
where
M: MatrixHost,
{
#[allow(unused_mut)]
fn gemv_inplace(&self, x: &Self::V, _t: Self::T, beta: Self::T, mut y: &mut Self::V) {
let nsmx: usize = NUM_SPECIES * NX;
for jy in 0..NX {
let yloc = nsmx * jy;
for jx in 0..NX {
let loc = yloc + NUM_SPECIES * jx;
for is in 0..NUM_SPECIES {
if is < NPREY {
y[loc + is] = x[loc + is] + beta * y[loc + is];
} else {
y[loc + is] = beta * y[loc + is];
}
}
}
}
}
fn sparsity(&self) -> Option<M::Sparsity> {
self.foodweb.mass_sparsity.clone()
}
}
struct FoodWebOut<'a, M, const NX: usize>
where
M: MatrixHost,
{
pub foodweb: &'a FoodWeb<M, NX>,
}
context_consts!(FoodWebOut);
impl<M, const NX: usize> Op for FoodWebOut<'_, M, NX>
where
M: MatrixHost,
{
type M = M;
type V = M::V;
type T = M::T;
type C = M::C;
fn nout(&self) -> usize {
2 * NUM_SPECIES
}
fn nparams(&self) -> usize {
0
}
fn nstates(&self) -> usize {
self.foodweb.context.nstates
}
fn context(&self) -> &Self::C {
self.foodweb.context()
}
}
impl<M, const NX: usize> NonLinearOp for FoodWebOut<'_, M, NX>
where
M: MatrixHost,
{
#[allow(unused_mut)]
fn call_inplace(&self, x: &M::V, _t: M::T, mut y: &mut M::V) {
let nsmx: usize = NUM_SPECIES * NX;
let jx_tl = 0;
let jy_tl = 0;
let jx_br = NX - 1;
let jy_br = NX - 1;
let loc_tl = NUM_SPECIES * jx_tl + nsmx * jy_tl;
let loc_br = NUM_SPECIES * jx_br + nsmx * jy_br;
for is in 0..NUM_SPECIES {
y[2 * is] = x[loc_tl + is];
y[2 * is + 1] = x[loc_br + is];
}
}
}
impl<M, const NX: usize> NonLinearOpJacobian for FoodWebOut<'_, M, NX>
where
M: MatrixHost,
{
#[allow(unused_mut)]
fn jac_mul_inplace(&self, _x: &Self::V, _t: Self::T, v: &Self::V, mut y: &mut Self::V) {
let nsmx: usize = NUM_SPECIES * NX;
let jx_tl = 0;
let jy_tl = 0;
let jx_br = NX - 1;
let jy_br = NX - 1;
let loc_tl = NUM_SPECIES * jx_tl + nsmx * jy_tl;
let loc_br = NUM_SPECIES * jx_br + nsmx * jy_br;
for is in 0..NUM_SPECIES {
y[2 * is] = v[loc_tl + is];
y[2 * is + 1] = v[loc_br + is];
}
}
}
struct FoodWeb<M, const NX: usize>
where
M: MatrixHost,
{
context: FoodWebContext<M, NX>,
rhs_sparsity: Option<M::Sparsity>,
rhs_coloring: Option<JacobianColoring<M>>,
mass_sparsity: Option<M::Sparsity>,
mass_coloring: Option<JacobianColoring<M>>,
}
impl<M, const NX: usize> FoodWeb<M, NX>
where
M: MatrixHost,
{
pub fn new(context: FoodWebContext<M, NX>, t0: M::T) -> Self {
let mut ret = Self {
context,
rhs_sparsity: None,
rhs_coloring: None,
mass_sparsity: None,
mass_coloring: None,
};
let init = FoodWebInit::new(&ret);
let y0 = init.call(t0);
let rhs = FoodWebRhs::new(&ret);
let non_zeros = find_jacobian_non_zeros(&rhs, &y0, t0);
ret.rhs_sparsity = Some(
MatrixSparsity::try_from_indices(rhs.nout(), rhs.nstates(), non_zeros.clone()).unwrap(),
);
ret.rhs_coloring = Some(JacobianColoring::new(
ret.rhs_sparsity.as_ref().unwrap(),
&non_zeros,
ret.context().clone(),
));
let mass = FoodWebMass::new(&ret);
let non_zeros = find_matrix_non_zeros(&mass, t0);
ret.mass_sparsity = Some(
MatrixSparsity::try_from_indices(mass.nout(), mass.nstates(), non_zeros.clone())
.unwrap(),
);
ret.mass_coloring = Some(JacobianColoring::new(
ret.mass_sparsity.as_ref().unwrap(),
&non_zeros,
ret.context().clone(),
));
ret
}
}
impl<M, const NX: usize> Op for FoodWeb<M, NX>
where
M: MatrixHost,
{
type M = M;
type V = M::V;
type T = M::T;
type C = M::C;
fn nout(&self) -> usize {
2 * NUM_SPECIES
}
fn nparams(&self) -> usize {
0
}
fn nstates(&self) -> usize {
self.context.nstates
}
fn context(&self) -> &Self::C {
&self.context.ctx
}
}
impl<'a, M, const NX: usize> OdeEquationsRef<'a> for FoodWeb<M, NX>
where
M: MatrixHost,
{
type Init = FoodWebInit<'a, M, NX>;
type Rhs = FoodWebRhs<'a, M, NX>;
type Mass = FoodWebMass<'a, M, NX>;
type Root = ParameterisedOp<'a, UnitCallable<M>>;
type Out = FoodWebOut<'a, M, NX>;
type Reset = ParameterisedOp<'a, UnitCallable<M>>;
}
impl<M, const NX: usize> OdeEquations for FoodWeb<M, NX>
where
M: MatrixHost,
{
fn rhs(&self) -> FoodWebRhs<'_, M, NX> {
FoodWebRhs::new(self)
}
fn init(&self) -> FoodWebInit<'_, M, NX> {
FoodWebInit::new(self)
}
fn mass(&self) -> Option<FoodWebMass<'_, M, NX>> {
Some(FoodWebMass::new(self))
}
fn out(&self) -> Option<FoodWebOut<'_, M, NX>> {
Some(FoodWebOut::new(self))
}
fn root(&self) -> Option<<Self as OdeEquationsRef<'_>>::Root> {
None
}
fn set_params(&mut self, _p: &Self::V) {
unimplemented!()
}
fn get_params(&self, _p: &mut Self::V) {
unimplemented!()
}
}
#[cfg(feature = "diffsl")]
struct FoodWebDiff<M, const NX: usize>
where
M: MatrixHost,
{
pub sparsity: Option<M::Sparsity>,
ctx: M::C,
}
#[cfg(feature = "diffsl")]
impl<M, const NX: usize> FoodWebDiff<M, NX>
where
M: MatrixHost,
{
pub fn new(y0: &M::V, t0: M::T) -> Self {
let mut ret = Self {
sparsity: None,
ctx: y0.context().clone(),
};
let non_zeros = find_jacobian_non_zeros(&ret, y0, t0);
ret.sparsity = Some(
MatrixSparsity::try_from_indices(ret.nout(), ret.nstates(), non_zeros.clone()).unwrap(),
);
ret
}
}
#[cfg(feature = "diffsl")]
impl<M, const NX: usize> Op for FoodWebDiff<M, NX>
where
M: MatrixHost,
{
type M = M;
type V = M::V;
type T = M::T;
type C = M::C;
fn nout(&self) -> usize {
NX * NX
}
fn nparams(&self) -> usize {
0
}
fn nstates(&self) -> usize {
NX * NX
}
fn context(&self) -> &Self::C {
&self.ctx
}
}
#[cfg(feature = "diffsl")]
impl<M, const NX: usize> NonLinearOp for FoodWebDiff<M, NX>
where
M: MatrixHost,
{
#[allow(unused_mut)]
fn call_inplace(&self, x: &M::V, _t: M::T, mut y: &mut M::V) {
let nsmx: usize = NX;
let dx = AX / (NX as f64 - 1.0);
let dy = AY / (NX as f64 - 1.0);
let cox = M::T::from_f64(1.0 / dx.powi(2)).unwrap();
let coy = M::T::from_f64(1.0 / dy.powi(2)).unwrap();
for jy in 0..NX {
let idyu = if jy != NX - 1 {
nsmx as i32
} else {
-(nsmx as i32)
};
let idyl = if jy != 0 { nsmx as i32 } else { -(nsmx as i32) };
for jx in 0..NX {
let idxu = if jx != NX - 1 { 1 } else { -1 };
let idxl = if jx != 0 { 1 } else { -1 };
let loc = jx + nsmx * jy;
let dcyli = x[loc] - x[usize::try_from(loc as i32 - idyl).unwrap()];
let dcyui = x[usize::try_from(loc as i32 + idyu).unwrap()] - x[loc];
let dcxli = x[loc] - x[usize::try_from(loc as i32 - idxl).unwrap()];
let dcxui = x[usize::try_from(loc as i32 + idxu).unwrap()] - x[loc];
y[loc] = coy * (dcyui - dcyli) + cox * (dcxui - dcxli);
}
}
}
}
#[cfg(feature = "diffsl")]
impl<M, const NX: usize> NonLinearOpJacobian for FoodWebDiff<M, NX>
where
M: MatrixHost,
{
#[allow(unused_mut)]
fn jac_mul_inplace(&self, _x: &M::V, _t: M::T, v: &M::V, mut y: &mut M::V) {
let nsmx: usize = NX;
let dx = AX / (NX as f64 - 1.0);
let dy = AY / (NX as f64 - 1.0);
let cox = M::T::from_f64(1.0 / dx.powi(2)).unwrap();
let coy = M::T::from_f64(1.0 / dy.powi(2)).unwrap();
for jy in 0..NX {
let idyu = if jy != NX - 1 {
nsmx as i32
} else {
-(nsmx as i32)
};
let idyl = if jy != 0 { nsmx as i32 } else { -(nsmx as i32) };
for jx in 0..NX {
let idxu = if jx != NX - 1 { 1 } else { -1 };
let idxl = if jx != 0 { 1 } else { -1 };
let loc = jx + nsmx * jy;
let dcyli = v[loc] - v[usize::try_from(loc as i32 - idyl).unwrap()];
let dcyui = v[usize::try_from(loc as i32 + idyu).unwrap()] - v[loc];
let dcxli = v[loc] - v[usize::try_from(loc as i32 - idxl).unwrap()];
let dcxui = v[usize::try_from(loc as i32 + idxu).unwrap()] - v[loc];
y[loc] = coy * (dcyui - dcyli) + cox * (dcxui - dcxli);
}
}
}
fn jacobian_sparsity(&self) -> Option<M::Sparsity> {
self.sparsity.clone()
}
}
fn soln<M: Matrix>(ctx: M::C) -> OdeSolverSolution<M::V> {
let mut soln = OdeSolverSolution {
solution_points: Vec::new(),
sens_solution_points: None,
rtol: M::T::from_f64(1e-4).unwrap(),
atol: M::V::from_element(2 * NUM_SPECIES, M::T::from_f64(1e-4).unwrap(), ctx.clone()),
negative_time: false,
};
let data = vec![
(vec![10.0, 10.0, 99999.0, 99949.0], 0.0),
(
vec![
9.997887753650794,
10.498336872161198,
99979.21262678975,
104933.61130371751,
],
0.001,
),
(
vec![
116.7394053543608,
141.3349347208864,
1167406.222331898,
1413309.7156706247,
],
0.01,
),
(
vec![
169.50991588474182,
196.55298551613117,
1695106.6267256583,
1965486.1821950572,
],
0.1,
),
(
vec![
169.50991230736778,
196.55298216342456,
1695106.5909521726,
1965486.1486681814,
],
0.4,
),
(
vec![
169.5099123071205,
196.55298216319915,
1695106.5909496995,
1965486.1486659276,
],
0.7,
),
(
vec![
169.50991230687316,
196.55298216297376,
1695106.5909472264,
1965486.1486636735,
],
1.0,
),
];
for (values, time) in data {
let values = M::V::from_vec(
values
.iter()
.map(|v| M::T::from_f64(*v).unwrap())
.collect::<Vec<_>>(),
ctx.clone(),
);
let time = M::T::from_f64(time).unwrap();
soln.push(values, time);
}
soln
}
#[allow(clippy::type_complexity)]
pub fn foodweb_problem<M, const NX: usize>() -> (
OdeSolverProblem<impl OdeEquationsImplicit<M = M, V = M::V, T = M::T, C = M::C>>,
OdeSolverSolution<M::V>,
)
where
M: MatrixHost,
{
let rtol = M::T::from_f64(1e-5).unwrap();
let ctx = M::C::default();
let atol = M::V::from_element(
NUM_SPECIES * NX * NX,
M::T::from_f64(1e-5).unwrap(),
ctx.clone(),
);
let t0 = M::T::zero();
let h0 = M::T::one();
let context = FoodWebContext::<M, NX>::new(ctx);
let eqn = FoodWeb::new(context, t0);
let problem = OdeSolverProblem::new(
eqn,
rtol,
atol,
None,
None,
None,
None,
None,
None,
t0,
h0,
false,
Default::default(),
Default::default(),
)
.unwrap();
let soln = soln::<M>(problem.context().clone());
(problem, soln)
}
#[cfg(test)]
mod tests {
use crate::{
matrix::dense_nalgebra_serial::NalgebraMat, scalar::Scale, ConstantOp, DenseMatrix,
LinearOp, MatrixCommon, NonLinearOp,
};
use super::*;
#[test]
fn test_jacobian() {
type M = NalgebraMat<f64>;
const NX: usize = 10;
let (problem, _soln) = foodweb_problem::<M, NX>();
let u0 = problem.eqn.init().call(0.0);
let jac = problem.eqn.rhs().jacobian(&u0, 0.0);
let h = 1e-5;
for i in 0..jac.ncols() {
let mut vplus = u0.clone();
let mut vminus = u0.clone();
vplus[i] += h;
vminus[i] -= h;
let yplus = problem.eqn.rhs().call(&vplus, 0.0);
let yminus = problem.eqn.rhs().call(&vminus, 0.0);
let fdiff = (yplus - yminus) * Scale(1.0 / (2.0 * h));
for j in 0..jac.nrows() {
assert!(
(jac.get_index(j, i) - fdiff[j]).abs() < 1e-1,
"jac[{}, {}] = {} (expect {})",
j,
i,
jac.get_index(j, i),
fdiff[j]
);
}
}
}
#[cfg(feature = "diffsl-llvm")]
#[test]
fn test_diffsl() {
use diffsl::LlvmModule;
type M = NalgebraMat<f64>;
const NX: usize = 10;
let (problem, _soln) = foodweb_problem::<M, NX>();
let u0 = problem.eqn.init().call(0.0);
let jac = problem.eqn.rhs().jacobian(&u0, 0.0);
let y0 = problem.eqn.rhs().call(&u0, 0.0);
let (problem_diffsl, _soln) = foodweb_diffsl_problem::<M, LlvmModule, NX>();
let u0_diffsl = problem_diffsl.eqn.init().call(0.0);
for i in 0..u0.len() {
let i_diffsl = if i % NUM_SPECIES >= NPREY {
NX * NX + i / NUM_SPECIES
} else {
i / NUM_SPECIES
};
assert!(
((u0[i] - u0_diffsl[i_diffsl]) / u0[i]).abs() < 1e-3,
"u0[{}] = {} (expect {})",
i,
u0[i],
u0_diffsl[i_diffsl]
);
}
let y0_diffsl = problem_diffsl.eqn.rhs().call(&u0_diffsl, 0.0);
for i in 0..y0.len() {
let i_diffsl = if i % NUM_SPECIES >= NPREY {
NX * NX + i / NUM_SPECIES
} else {
i / NUM_SPECIES
};
assert!(
((y0[i] - y0_diffsl[i_diffsl]) / y0[i]).abs() < 1e-3,
"y0[{}] = {} (expect {})",
i,
y0[i],
y0_diffsl[i_diffsl]
);
}
let jac_diffsl = problem_diffsl.eqn.rhs().jacobian(&u0_diffsl, 0.0);
for i in 0..jac.ncols() {
for j in 0..jac.nrows() {
let i_diffsl = if i % NUM_SPECIES >= NPREY {
NX * NX + i / NUM_SPECIES
} else {
i / NUM_SPECIES
};
let j_diffsl = if j % NUM_SPECIES >= NPREY {
NX * NX + j / NUM_SPECIES
} else {
j / NUM_SPECIES
};
assert!(
(jac.get_index(j, i) - jac_diffsl.get_index(j_diffsl, i_diffsl)).abs() < 1e-3,
"jac[{}, {}] = {} (expect {})",
j,
i,
jac.get_index(j, i),
jac_diffsl.get_index(j_diffsl, i_diffsl)
);
}
}
}
#[test]
fn test_mass() {
type M = NalgebraMat<f64>;
const NX: usize = 10;
let (problem, _soln) = foodweb_problem::<M, NX>();
let mass = problem.eqn.mass().unwrap().matrix(0.0);
for i in 0..mass.ncols() {
for j in 0..mass.nrows() {
if i == j && i % NUM_SPECIES < NPREY {
assert_eq!(mass.get_index(i, j), 1.0);
} else {
assert_eq!(mass.get_index(i, j), 0.0);
}
}
}
}
}