use crate::methods::finitedifferences::meshers::FdmMesher;
use crate::shared::Shared;
use crate::types::Size;
use super::triplebandlinearop::TripleBandLinearOp;
pub fn first_derivative_op(direction: Size, mesher: Shared<dyn FdmMesher>) -> TripleBandLinearOp {
let extent = mesher.layout().dim()[direction];
TripleBandLinearOp::with_bands(direction, mesher, move |mesher, position| {
let hm = mesher.dminus(position, direction);
let hp = mesher.dplus(position, direction);
let zetam1 = hm * (hm + hp);
let zeta0 = hm * hp;
let zetap1 = hp * (hm + hp);
let coordinate = position.coordinates()[direction];
if coordinate == 0 {
let upper = 1.0 / hp;
(0.0, -upper, upper)
} else if coordinate == extent - 1 {
let diag = 1.0 / hm;
(-diag, diag, 0.0)
} else {
(-hp / zetam1, (hp - hm) / zeta0, hm / zetap1)
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::array::Array;
use crate::methods::finitedifferences::meshers::UniformGridMesher;
use crate::methods::finitedifferences::operators::{FdmLinearOp, FdmLinearOpLayout};
use crate::shared::shared;
use crate::types::Real;
#[test]
fn differences_a_ramp_to_one_everywhere() {
let layout = shared(FdmLinearOpLayout::new(vec![5]));
let mesher: Shared<dyn FdmMesher> =
shared(UniformGridMesher::new(Shared::clone(&layout), &[(0.0, 1.0)]).unwrap());
let x = mesher.locations(0);
let t = first_derivative_op(0, mesher).apply(&x);
for i in 0..t.size() {
assert!((t[i] - 1.0).abs() <= 1e-12, "at {i}: {}", t[i]);
}
}
#[test]
fn first_derivatives_map_apply_matches_quantlib() {
let dim = [400, 100, 50];
let layout = shared(FdmLinearOpLayout::new(dim.to_vec()));
let boundaries = [(-5.0, 5.0), (0.0, 10.0), (5.0, 15.0)];
let mesher: Shared<dyn FdmMesher> =
shared(UniformGridMesher::new(Shared::clone(&layout), &boundaries).unwrap());
let map = first_derivative_op(2, Shared::clone(&mesher));
let mut r = Array::with_size(layout.size());
let mut position = layout.begin();
while position.index() < layout.size() {
r[position.index()] =
mesher.location(&position, 0).sin() + mesher.location(&position, 2).cos();
position.advance();
}
let t = map.apply(&r);
let (z_min, z_max) = boundaries[2];
let dz = (z_max - z_min) / (dim[2] - 1) as Real;
let mut position = layout.begin();
while position.index() < layout.size() {
let z = position.coordinates()[2];
let z0 = if z > 0 { z - 1 } else { 1 };
let z2 = if z < dim[2] - 1 { z + 1 } else { dim[2] - 2 };
let lz0 = z_min + z0 as Real * dz;
let lz2 = z_min + z2 as Real * dz;
let expected = if z == 0 {
((z_min + dz).cos() - z_min.cos()) / dz
} else if z == dim[2] - 1 {
(z_max.cos() - (z_max - dz).cos()) / dz
} else {
(lz2.cos() - lz0.cos()) / (2.0 * dz)
};
let calculated = t[position.index()];
assert!(
(calculated - expected).abs() <= 1e-10,
"first derivative at {}: {calculated} != {expected}",
position.index()
);
position.advance();
}
}
}