#![allow(clippy::float_cmp)]
use legendre::{
core::{
state::{State, StateBuilder},
storage::{DenseStorage, SystemAllocator},
},
geometry::{
GridError,
cartesian::{CartesianGrid, fill_ghosts_mirror, for_each_box, for_each_interior},
grid::{BlockId, Grid},
},
};
type DenseState = State<f64, DenseStorage<f64>>;
mod grid {
use super::*;
#[test]
fn construction_rejects_bad_inputs() {
assert!(matches!(
CartesianGrid::new([0, 4], [1, 2], [0.0; 2], [1.0; 2]),
Err(GridError::EmptyDimension(0))
));
assert!(matches!(
CartesianGrid::new([4, 4], [3, 2], [0.0; 2], [1.0; 2]),
Err(GridError::IndivisibleDimension { dimension: 0, .. })
));
for bad in [0.0, -1.0, f64::NAN, f64::INFINITY] {
assert!(matches!(
CartesianGrid::new([4, 4], [2, 2], [0.0; 2], [1.0, bad]),
Err(GridError::InvalidSpacing { dimension: 1, .. })
));
}
}
#[test]
fn block_topology_is_dimension_0_fastest() {
let grid = CartesianGrid::new([4, 4], [2, 2], [0.0; 2], [1.0; 2]).unwrap();
assert_eq!(grid.num_blocks(), 4);
assert_eq!(grid.block_coords(BlockId(1)), [1, 0]);
assert_eq!(grid.block_coords(BlockId(2)), [0, 1]);
assert_eq!(grid.face_neighbor(BlockId(0), 0, 1), Some(BlockId(1)));
assert_eq!(grid.face_neighbor(BlockId(0), 1, 1), Some(BlockId(2)));
assert_eq!(grid.face_neighbor(BlockId(0), 0, -1), None);
assert_eq!(grid.face_neighbor(BlockId(3), 1, 1), None);
assert_eq!(grid.face_neighbor(BlockId(3), 0, -1), Some(BlockId(2)));
}
#[test]
fn cell_centers_are_cell_centered() {
let grid = CartesianGrid::new([4], [2], [10.0], [0.5]).unwrap();
assert_eq!(grid.cell_center(BlockId(0), [0]), [10.25]);
assert_eq!(grid.cell_center(BlockId(1), [1]), [11.75]);
}
#[test]
fn iteration_is_dimension_0_fastest() {
let mut seen = Vec::new();
for_each_interior([2, 2], |idx| seen.push(idx));
assert_eq!(seen, vec![[0, 0], [1, 0], [0, 1], [1, 1]]);
let mut boxed = Vec::new();
for_each_box([-1, 0], [1, 1], |idx| boxed.push(idx));
assert_eq!(boxed, vec![[-1, 0], [0, 0]]);
for_each_interior([0, 3], |_| panic!("empty extent must not iterate"));
for_each_box([2, 0], [2, 5], |_| panic!("empty box must not iterate"));
}
}
mod ghosts {
use super::*;
#[test]
fn halo_and_mirror_values_are_exact() {
let grid = CartesianGrid::new([8], [4], [0.0], [1.0]).unwrap();
let mut builder = StateBuilder::<f64>::new();
let u = builder.register("u", 1);
let mut state: DenseState = builder.build(&grid, &SystemAllocator);
for b in 0..2 {
let mut v = state.view_mut(&grid, BlockId(b), u);
for_each_interior([4], |[i]| {
v.set([i], (b as usize * 4 + i as usize) as f64);
});
}
fill_ghosts_mirror(&grid, &mut state, u);
let v0 = state.view(&grid, BlockId(0), u);
let v1 = state.view(&grid, BlockId(1), u);
assert_eq!(v0.get([-1]), 0.0);
assert_eq!(v1.get([4]), 7.0);
assert_eq!(v0.get([4]), 4.0);
assert_eq!(v1.get([-1]), 3.0);
}
#[test]
fn wide_ghosts_fill_every_layer() {
let grid = CartesianGrid::new([8], [4], [0.0], [1.0]).unwrap();
let mut builder = StateBuilder::<f64>::new();
let u = builder.register("u", 2);
let mut state: DenseState = builder.build(&grid, &SystemAllocator);
for b in 0..2 {
let mut v = state.view_mut(&grid, BlockId(b), u);
for_each_interior([4], |[i]| {
v.set([i], (b as usize * 4 + i as usize) as f64);
});
}
fill_ghosts_mirror(&grid, &mut state, u);
let v0 = state.view(&grid, BlockId(0), u);
assert_eq!(v0.get([-1]), 0.0);
assert_eq!(v0.get([-2]), 1.0);
assert_eq!(v0.get([4]), 4.0);
assert_eq!(v0.get([5]), 5.0);
}
}
mod state_ops {
use super::*;
fn two_field_state() -> (
CartesianGrid<1>,
DenseState,
legendre::core::state::FieldHandle<f64>,
legendre::core::state::FieldHandle<f64>,
) {
let grid = CartesianGrid::new([4], [4], [0.0], [1.0]).unwrap();
let mut builder = StateBuilder::<f64>::new();
let dynamic = builder.register("dyn", 0);
let static_f = builder.register_static("static", 0);
let mut state: DenseState = builder.build(&grid, &SystemAllocator);
{
let mut v = state.view_mut(&grid, BlockId(0), dynamic);
for_each_interior([4], |idx| v.set(idx, 1.0));
}
{
let mut v = state.view_mut(&grid, BlockId(0), static_f);
for_each_interior([4], |idx| v.set(idx, 5.0));
}
(grid, state, dynamic, static_f)
}
#[test]
fn tendency_buffers_carry_no_static_storage() {
let (grid, state, dynamic, static_f) = two_field_state();
let tendency = state.like_tendency(&grid, &SystemAllocator);
assert_eq!(tendency.slab(BlockId(0), static_f).len(), 0);
assert_eq!(tendency.slab(BlockId(0), dynamic).len(), 4);
let full = state.like(&grid, &SystemAllocator);
assert_eq!(full.slab(BlockId(0), static_f).len(), 4);
}
#[test]
fn axpy_and_noise_skip_static_fields() {
let (grid, mut state, dynamic, static_f) = two_field_state();
let mut tendency = state.like_tendency(&grid, &SystemAllocator);
tendency.slab_mut(BlockId(0), dynamic).fill(2.0);
state.axpy(10.0, &tendency);
let vd = state.view(&grid, BlockId(0), dynamic);
let vs = state.view(&grid, BlockId(0), static_f);
for_each_interior([4], |idx| {
assert_eq!(vd.get(idx), 21.0, "dynamic: 1 + 10·2");
assert_eq!(vs.get(idx), 5.0, "static fields must be skipped");
});
let mut amp = state.like_tendency(&grid, &SystemAllocator);
amp.slab_mut(BlockId(0), dynamic).fill(1.0);
let before_static: Vec<f64> = state.slab(BlockId(0), static_f).to_vec();
let before_dynamic: Vec<f64> = state.slab(BlockId(0), dynamic).to_vec();
state.add_noise(&, 1.0, 42, 0);
assert_eq!(
state.slab(BlockId(0), static_f),
&before_static[..],
"static fields receive no noise"
);
assert_ne!(
state.slab(BlockId(0), dynamic),
&before_dynamic[..],
"dynamic fields do"
);
}
#[test]
fn zero_amplitude_entries_receive_no_noise() {
let (grid, mut state, dynamic, _) = two_field_state();
let amp = state.like_tendency(&grid, &SystemAllocator); let before: Vec<f64> = state.slab(BlockId(0), dynamic).to_vec();
state.add_noise(&, 1.0, 42, 0);
assert_eq!(state.slab(BlockId(0), dynamic), &before[..]);
let _ = grid;
}
#[test]
fn view_split_borrows_both_orders() {
let grid = CartesianGrid::new([2], [2], [0.0], [1.0]).unwrap();
let mut builder = StateBuilder::<f64>::new();
let a = builder.register("a", 0);
let b = builder.register("b", 0);
let mut state: DenseState = builder.build(&grid, &SystemAllocator);
state.slab_mut(BlockId(0), a).fill(1.0);
state.slab_mut(BlockId(0), b).fill(2.0);
let (layout, blocks) = state.split_blocks_mut();
let mut block = blocks[0].bind_mut(layout);
{
let (mut wa, rb) = block.view_split(&grid, BlockId(0), a, b);
wa.set([0], rb.get([0]) + 10.0);
}
{
let (mut wb, ra) = block.view_split(&grid, BlockId(0), b, a);
wb.set([1], ra.get([1]) + 100.0);
}
assert_eq!(state.view(&grid, BlockId(0), a).get([0]), 12.0);
assert_eq!(state.view(&grid, BlockId(0), b).get([1]), 101.0);
}
#[test]
#[should_panic(expected = "cannot split a field with itself")]
fn view_split_rejects_same_field() {
let grid = CartesianGrid::new([2], [2], [0.0], [1.0]).unwrap();
let mut builder = StateBuilder::<f64>::new();
let a = builder.register("a", 0);
let mut state: DenseState = builder.build(&grid, &SystemAllocator);
let (layout, blocks) = state.split_blocks_mut();
let mut block = blocks[0].bind_mut(layout);
let _ = block.view_split(&grid, BlockId(0), a, a);
}
#[test]
#[should_panic(expected = "two distinct")]
fn slab_pair_rejects_same_block() {
let grid = CartesianGrid::new([4], [4], [0.0], [1.0]).unwrap();
let mut builder = StateBuilder::<f64>::new();
let a = builder.register("a", 0);
let mut state: DenseState = builder.build(&grid, &SystemAllocator);
let _ = state.slab_pair_mut(BlockId(0), BlockId(0), a);
}
#[test]
fn fill_zero_resets_every_slab() {
let (grid, mut state, dynamic, static_f) = two_field_state();
state.fill_zero();
let vd = state.view(&grid, BlockId(0), dynamic);
let vs = state.view(&grid, BlockId(0), static_f);
for_each_interior([4], |idx| {
assert_eq!(vd.get(idx), 0.0);
assert_eq!(vs.get(idx), 0.0);
});
}
}