use crate::Side;
use crate::StrError;
use russell_lab::math::chebyshev_lobatto_points;
use russell_lab::Vector;
pub struct Grid1d {
nx: usize,
coords: Vector,
nodes_xmin: Vec<usize>,
nodes_xmax: Vec<usize>,
}
impl Grid1d {
pub fn new(xx: &[f64]) -> Result<Self, StrError> {
let nx = xx.len();
if nx < 2 {
return Err("nx must be ≥ 2");
}
for i in 1..nx {
if xx[i] <= xx[i - 1] {
return Err("xx must be strictly increasing");
}
}
Ok(Self {
nx,
coords: Vector::from(&xx),
nodes_xmin: vec![0],
nodes_xmax: vec![nx - 1],
})
}
pub fn new_uniform(xmin: f64, xmax: f64, nx: usize) -> Result<Self, StrError> {
if nx < 2 {
return Err("nx must be ≥ 2");
}
if xmax <= xmin {
return Err("xmax must be > xmin");
}
let dx = (xmax - xmin) / ((nx - 1) as f64);
let mut coords = Vector::new(nx);
for i in 0..nx {
let x = xmin + (i as f64) * dx;
coords[i] = x;
}
Ok(Self {
nx,
coords,
nodes_xmin: vec![0],
nodes_xmax: vec![nx - 1],
})
}
pub fn new_chebyshev_gauss_lobatto(nx: usize) -> Result<Self, StrError> {
if nx < 2 {
return Err("nx must be ≥ 2");
}
Ok(Self {
nx,
coords: chebyshev_lobatto_points(nx - 1),
nodes_xmin: vec![0],
nodes_xmax: vec![nx - 1],
})
}
pub fn nx(&self) -> usize {
self.nx
}
pub fn is_xmin(&self, m: usize) -> bool {
m == 0
}
pub fn is_xmax(&self, m: usize) -> bool {
m == self.nx - 1
}
pub fn on_boundary(&self, m: usize) -> bool {
m == 0 || m == self.nx - 1
}
pub fn get_nodes_on_side(&self, side: Side) -> &[usize] {
match side {
Side::Xmin => &self.nodes_xmin,
Side::Xmax => &self.nodes_xmax,
_ => panic!("invalid side for 1D grid"),
}
}
pub fn get_boundary_nodes(&self) -> (&[usize], &[usize]) {
(&self.nodes_xmin, &self.nodes_xmax)
}
pub fn get_dx(&self) -> Option<f64> {
let mut dx = f64::NEG_INFINITY;
for i in 1..self.nx {
let x = self.coords[i];
let xl = self.coords[i - 1];
if dx == f64::NEG_INFINITY {
dx = x - xl;
assert!(dx > 0.0);
} else if f64::abs(x - xl - dx) > 10.0 * f64::EPSILON {
return None; }
}
Some(dx)
}
pub fn coord(&self, m: usize) -> f64 {
self.coords[m]
}
pub fn for_each_coord(&self, mut f: impl FnMut(usize, f64)) {
for m in 0..self.nx {
f(m, self.coords[m]);
}
}
}
#[cfg(test)]
mod tests {
use super::Grid1d;
#[test]
fn new_fails_on_invalid_input() {
assert_eq!(Grid1d::new(&[0.0]).err(), Some("nx must be ≥ 2"));
assert_eq!(Grid1d::new(&[0.0, 0.0]).err(), Some("xx must be strictly increasing"));
assert_eq!(Grid1d::new(&[1.0, 0.0]).err(), Some("xx must be strictly increasing"));
}
#[test]
fn new_uniform_fails_on_invalid_input() {
assert_eq!(Grid1d::new_uniform(0.0, 1.0, 1).err(), Some("nx must be ≥ 2"));
assert_eq!(Grid1d::new_uniform(1.0, 0.0, 4).err(), Some("xmax must be > xmin"));
assert_eq!(Grid1d::new_uniform(1.0, 1.0, 4).err(), Some("xmax must be > xmin"));
}
#[test]
fn new_works() {
let xx = &[-3.0, -2.9, 2.9, 3.0];
let correct_coords = vec![-3.0, -2.9, 2.9, 3.0];
let grid = Grid1d::new(xx).unwrap();
assert_eq!(grid.nx, 4);
assert_eq!(grid.coords.as_data(), &correct_coords);
assert_eq!(grid.nx(), 4);
assert_eq!(grid.get_dx(), None);
let mut coords = Vec::new();
grid.for_each_coord(|_m, x| coords.push(x));
assert_eq!(coords, correct_coords);
}
#[test]
fn new_uniform_works() {
let xmin = -3.0;
let xmax = 3.0;
let nx = 4;
let correct_coords = vec![-3.0, -1.0, 1.0, 3.0];
let grid = Grid1d::new_uniform(xmin, xmax, nx).unwrap();
assert_eq!(grid.nx, 4);
assert_eq!(grid.coords.as_data(), &correct_coords);
assert_eq!(grid.nx(), 4);
assert_eq!(grid.get_dx(), Some(2.0));
let mut coords = Vec::new();
grid.for_each_coord(|_m, x| coords.push(x));
assert_eq!(coords, correct_coords);
}
#[test]
fn coord_works() {
let grid = Grid1d::new_uniform(0.0, 4.0, 5).unwrap();
assert_eq!(grid.coord(0), 0.0);
assert_eq!(grid.coord(1), 1.0);
assert_eq!(grid.coord(2), 2.0);
assert_eq!(grid.coord(3), 3.0);
assert_eq!(grid.coord(4), 4.0);
}
#[test]
fn boundary_nodes_work() {
let grid = Grid1d::new_uniform(0.0, 10.0, 6).unwrap();
assert_eq!(grid.is_xmin(0), true);
assert_eq!(grid.is_xmin(1), false);
assert_eq!(grid.is_xmax(5), true);
assert_eq!(grid.is_xmax(4), false);
assert_eq!(grid.get_boundary_nodes(), (&[0][..], &[5][..]));
}
#[test]
fn get_dx_works_uniform() {
let grid = Grid1d::new_uniform(0.0, 3.0, 31).unwrap();
assert_eq!(grid.get_dx(), Some(0.1));
}
#[test]
fn get_dx_captures_non_uniform() {
let xx = &[0.0, 0.1, 0.5, 1.0];
let grid = Grid1d::new(xx).unwrap();
assert_eq!(grid.get_dx(), None);
}
#[test]
fn get_dx_uniform_grids() {
let grid = Grid1d::new_uniform(0.0, 6.0, 4).unwrap();
assert_eq!(grid.get_dx(), Some(2.0));
let grid = Grid1d::new_uniform(0.0, 1.0, 5).unwrap();
assert_eq!(grid.get_dx(), Some(0.25));
let grid = Grid1d::new_uniform(0.0, 1.0, 2).unwrap();
assert_eq!(grid.get_dx(), Some(1.0));
}
#[test]
fn get_dx_non_uniform_grids() {
let xx = &[0.0, 0.1, 0.5, 1.0];
let grid = Grid1d::new(xx).unwrap();
assert_eq!(grid.get_dx(), None);
let xx = &[0.1, 1.0, 10.0, 100.0];
let grid = Grid1d::new(xx).unwrap();
assert_eq!(grid.get_dx(), None);
}
#[test]
fn get_dx_uniform_from_arrays() {
let xx = &[0.0, 1.0, 2.0, 3.0, 4.0]; let grid = Grid1d::new(xx).unwrap();
assert_eq!(grid.get_dx(), Some(1.0));
let xx = &[-2.0, -1.0, 0.0, 1.0]; let grid = Grid1d::new(xx).unwrap();
assert_eq!(grid.get_dx(), Some(1.0));
let xx = &[0.0, 0.25, 0.5, 0.75, 1.0]; let grid = Grid1d::new(xx).unwrap();
let result = grid.get_dx().unwrap();
assert!((result - 0.25).abs() < 1e-15);
}
#[test]
fn get_dx_precision_edge_cases() {
let grid = Grid1d::new_uniform(0.0, 1e-6, 3).unwrap();
let result = grid.get_dx().unwrap();
assert!((result - 5e-7).abs() < 1e-21);
let grid = Grid1d::new_uniform(0.0, 1e6, 3).unwrap();
let result = grid.get_dx().unwrap();
assert!((result - 5e5).abs() < 1e-9);
let mut xx = vec![0.0, 1.0, 2.0, 3.0];
xx[2] += 11.0 * f64::EPSILON; let grid = Grid1d::new(&xx).unwrap();
assert_eq!(grid.get_dx(), None);
let mut xx = vec![0.0, 1.0, 2.0, 3.0];
xx[2] += f64::EPSILON / 2.0; let grid = Grid1d::new(&xx).unwrap();
assert_eq!(grid.get_dx(), Some(1.0)); }
#[test]
fn get_dx_different_grid_sizes() {
let grid = Grid1d::new_uniform(0.0, 3.0, 2).unwrap();
assert_eq!(grid.get_dx(), Some(3.0));
let grid = Grid1d::new_uniform(0.0, 9.0, 10).unwrap();
assert_eq!(grid.get_dx(), Some(1.0));
let grid = Grid1d::new_uniform(0.0, 1.0, 50).unwrap();
let result = grid.get_dx().unwrap();
assert!((result - 1.0 / 49.0).abs() < 1e-15);
}
#[test]
fn get_dx_boundary_coordinates() {
let grid = Grid1d::new_uniform(-1.0, 1.0, 3).unwrap();
assert_eq!(grid.get_dx(), Some(1.0));
let grid = Grid1d::new_uniform(0.0, 1e-10, 2).unwrap();
let result = grid.get_dx().unwrap();
assert!((result - 1e-10).abs() < 1e-25);
let grid = Grid1d::new_uniform(1e6, 1e6 + 4.0, 3).unwrap();
assert_eq!(grid.get_dx(), Some(2.0));
}
#[test]
fn for_each_coord_works() {
let grid = Grid1d::new_uniform(0.0, 2.0, 3).unwrap();
let mut indices = Vec::new();
let mut coords = Vec::new();
grid.for_each_coord(|m, x| {
indices.push(m);
coords.push(x);
});
assert_eq!(indices, vec![0, 1, 2]);
assert_eq!(coords, vec![0.0, 1.0, 2.0]);
}
#[test]
fn for_each_coord_empty_closure() {
let grid = Grid1d::new_uniform(0.0, 1.0, 2).unwrap();
grid.for_each_coord(|_, _| {});
}
#[test]
fn size_method_works() {
let grid = Grid1d::new_uniform(0.0, 1.0, 5).unwrap();
assert_eq!(grid.nx(), 5);
}
#[test]
fn constructor_with_single_spacing() {
let grid = Grid1d::new_uniform(5.0, 7.0, 2).unwrap();
assert_eq!(grid.nx(), 2);
assert_eq!(grid.coord(0), 5.0);
assert_eq!(grid.coord(1), 7.0);
assert_eq!(grid.get_dx(), Some(2.0));
}
#[test]
fn constructor_with_negative_domain() {
let grid = Grid1d::new_uniform(-5.0, -1.0, 5).unwrap();
assert_eq!(grid.coord(0), -5.0);
assert_eq!(grid.coord(4), -1.0);
assert_eq!(grid.get_dx(), Some(1.0)); }
#[test]
fn non_uniform_constructor_various_cases() {
let xx = &[0.0, 0.1, 0.3, 0.7, 1.5];
let grid = Grid1d::new(xx).unwrap();
assert_eq!(grid.nx(), 5);
assert_eq!(grid.get_dx(), None);
let xx = &[0.0, 0.8, 1.2, 1.4, 1.5];
let grid = Grid1d::new(xx).unwrap();
assert_eq!(grid.nx(), 5);
assert_eq!(grid.get_dx(), None);
}
#[test]
fn boundary_node_indices() {
let grid = Grid1d::new_uniform(0.0, 1.0, 2).unwrap();
assert_eq!(grid.is_xmin(0), true);
assert_eq!(grid.is_xmax(1), true);
let grid = Grid1d::new_uniform(0.0, 1.0, 10).unwrap();
assert_eq!(grid.is_xmin(0), true);
assert_eq!(grid.is_xmax(9), true);
let grid = Grid1d::new_uniform(0.0, 1.0, 100).unwrap();
assert_eq!(grid.is_xmin(0), true);
assert_eq!(grid.is_xmax(99), true);
}
#[test]
fn documentation_examples_work() {
let xx = &[0.0, 0.1, 0.5, 0.9, 1.0];
let grid = Grid1d::new(xx).unwrap();
assert_eq!(grid.nx(), 5);
let grid = Grid1d::new_uniform(0.0, 1.0, 5).unwrap();
assert_eq!(grid.coord(0), 0.0);
assert_eq!(grid.coord(4), 1.0);
let grid = Grid1d::new_uniform(0.0, 1.0, 3).unwrap();
let mut coords = Vec::new();
grid.for_each_coord(|_m, x| coords.push(x));
assert_eq!(coords.len(), 3);
}
#[test]
fn new_chebyshev_gauss_lobatto_works() {
let grid = Grid1d::new_chebyshev_gauss_lobatto(3).unwrap();
assert_eq!(grid.nx(), 3);
assert!((grid.coord(0) - (-1.0)).abs() < 1e-15);
assert!((grid.coord(1) - 0.0).abs() < 1e-15);
assert!((grid.coord(2) - 1.0).abs() < 1e-15);
}
#[test]
fn new_chebyshev_gauss_lobatto_fails_on_invalid_input() {
assert_eq!(Grid1d::new_chebyshev_gauss_lobatto(1).err(), Some("nx must be ≥ 2"));
}
#[test]
fn on_boundary_works() {
let grid = Grid1d::new_uniform(0.0, 1.0, 3).unwrap();
assert!(grid.on_boundary(0));
assert!(!grid.on_boundary(1));
assert!(grid.on_boundary(2));
}
#[test]
fn get_nodes_on_side_works() {
let grid = Grid1d::new_uniform(0.0, 1.0, 3).unwrap();
assert_eq!(grid.get_nodes_on_side(crate::Side::Xmin), &[0]);
assert_eq!(grid.get_nodes_on_side(crate::Side::Xmax), &[2]);
}
#[test]
#[should_panic(expected = "invalid side for 1D grid")]
fn get_nodes_on_side_panics_on_invalid_side() {
let grid = Grid1d::new_uniform(0.0, 1.0, 3).unwrap();
grid.get_nodes_on_side(crate::Side::Ymin);
}
}