use crate::mesh::Mesh;
#[derive(Debug)]
pub struct UniformGrid1D {
n_points: usize,
dx: f64,
nodes: Vec<[f64; 1]>,
}
impl UniformGrid1D {
pub fn new(n_points: usize, x_start: f64, x_end: f64) -> Result<Self, String> {
if n_points < 2 {
return Err(format!(
"UniformGrid1D requires at least 2 nodes, got {}",
n_points
));
}
if x_end <= x_start {
return Err(format!(
"UniformGrid1D requires x_end > x_start, got [{}, {}]",
x_start, x_end
));
}
let dx = (x_end - x_start) / (n_points - 1) as f64;
let nodes = (0..n_points).map(|i| [x_start + i as f64 * dx]).collect();
Ok(Self {
n_points,
dx,
nodes,
})
}
}
impl Mesh for UniformGrid1D {
fn n_dof(&self) -> usize {
self.n_points
}
fn coordinates(&self, i: usize) -> &[f64] {
&self.nodes[i]
}
fn spatial_dimension(&self) -> usize {
1
}
fn characteristic_length(&self) -> f64 {
self.dx
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_valid_grid_succeeds() {
let grid = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
assert_eq!(grid.n_dof(), 5);
}
#[test]
fn new_with_one_node_fails() {
assert!(UniformGrid1D::new(1, 0.0, 1.0).is_err());
}
#[test]
fn new_with_zero_nodes_fails() {
assert!(UniformGrid1D::new(0, 0.0, 1.0).is_err());
}
#[test]
fn new_with_reversed_domain_fails() {
assert!(UniformGrid1D::new(10, 1.0, 0.0).is_err());
}
#[test]
fn new_with_equal_bounds_fails() {
assert!(UniformGrid1D::new(10, 0.5, 0.5).is_err());
}
#[test]
fn error_message_mentions_constraint() {
let err = UniformGrid1D::new(1, 0.0, 1.0).unwrap_err();
assert!(err.contains("2"));
let err = UniformGrid1D::new(10, 1.0, 0.0).unwrap_err();
assert!(err.contains("x_end"));
}
#[test]
fn n_dof_equals_n_points() {
for n in [2, 10, 100, 1000] {
let grid = UniformGrid1D::new(n, 0.0, 1.0).unwrap();
assert_eq!(grid.n_dof(), n);
}
}
#[test]
fn first_node_at_x_start() {
let grid = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
assert!((grid.coordinates(0)[0] - 0.0).abs() < 1e-12);
}
#[test]
fn last_node_at_x_end() {
let grid = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
assert!((grid.coordinates(4)[0] - 1.0).abs() < 1e-12);
}
#[test]
fn middle_node_correctly_positioned() {
let grid = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
assert!((grid.coordinates(2)[0] - 0.5).abs() < 1e-12);
}
#[test]
fn coordinates_with_non_zero_x_start() {
let grid = UniformGrid1D::new(3, 1.0, 3.0).unwrap();
assert!((grid.coordinates(0)[0] - 1.0).abs() < 1e-12);
assert!((grid.coordinates(1)[0] - 2.0).abs() < 1e-12);
assert!((grid.coordinates(2)[0] - 3.0).abs() < 1e-12);
}
#[test]
fn coordinates_slice_has_length_one() {
let grid = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
for i in 0..grid.n_dof() {
assert_eq!(grid.coordinates(i).len(), 1);
}
}
#[test]
fn coordinates_are_strictly_increasing() {
let grid = UniformGrid1D::new(10, 0.0, 1.0).unwrap();
for i in 1..grid.n_dof() {
assert!(grid.coordinates(i)[0] > grid.coordinates(i - 1)[0]);
}
}
#[test]
fn coordinates_zero_allocation_returns_reference() {
let grid = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
let ptr1 = grid.coordinates(2).as_ptr();
let ptr2 = grid.coordinates(2).as_ptr();
assert_eq!(ptr1, ptr2);
}
#[test]
#[should_panic]
fn coordinates_out_of_bounds_panics() {
let grid = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
let _ = grid.coordinates(5); }
#[test]
fn spatial_dimension_is_one() {
let grid = UniformGrid1D::new(10, 0.0, 1.0).unwrap();
assert_eq!(grid.spatial_dimension(), 1);
}
#[test]
fn characteristic_length_equals_dx() {
let grid = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
assert!((grid.characteristic_length() - 0.25).abs() < 1e-12);
}
#[test]
fn characteristic_length_halves_when_doubling_nodes() {
let coarse = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
let fine = UniformGrid1D::new(9, 0.0, 1.0).unwrap();
let ratio = coarse.characteristic_length() / fine.characteristic_length();
assert!((ratio - 2.0).abs() < 1e-12);
}
#[test]
fn characteristic_length_scales_with_domain() {
let unit = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
let large = UniformGrid1D::new(5, 0.0, 4.0).unwrap();
let ratio = large.characteristic_length() / unit.characteristic_length();
assert!((ratio - 4.0).abs() < 1e-12);
}
#[test]
fn usable_as_dyn_mesh() {
let grid: Box<dyn Mesh> = Box::new(UniformGrid1D::new(10, 0.0, 1.0).unwrap());
assert_eq!(grid.n_dof(), 10);
assert_eq!(grid.spatial_dimension(), 1);
assert!((grid.characteristic_length() - 1.0 / 9.0).abs() < 1e-12);
}
#[test]
fn mesh_vec_of_different_grids() {
let grids: Vec<Box<dyn Mesh>> = vec![
Box::new(UniformGrid1D::new(10, 0.0, 1.0).unwrap()),
Box::new(UniformGrid1D::new(50, 0.0, 0.25).unwrap()),
];
assert_eq!(grids[0].n_dof(), 10);
assert_eq!(grids[1].n_dof(), 50);
}
#[test]
fn uniform_grid_1d_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<UniformGrid1D>();
}
#[test]
fn inv1_dx_not_accessible_directly() {
let grid = UniformGrid1D::new(5, 0.0, 1.0).unwrap();
let dx = grid.characteristic_length(); assert!((dx - 0.25).abs() < 1e-12);
}
#[test]
fn inv1_n_points_not_accessible_directly() {
let grid = UniformGrid1D::new(7, 0.0, 1.0).unwrap();
assert_eq!(grid.n_dof(), 7);
}
}