use crate::Side;
use crate::StrError;
use russell_lab::math::chebyshev_lobatto_points;
pub struct Grid2d {
xmin: f64,
xmax: f64,
ymin: f64,
ymax: f64,
nx: usize,
ny: usize,
npoint: usize,
coords: Vec<(f64, f64)>,
nodes_xmin: Vec<usize>,
nodes_xmax: Vec<usize>,
nodes_ymin: Vec<usize>,
nodes_ymax: Vec<usize>,
is_chebyshev_gauss_lobatto: bool,
}
impl Grid2d {
fn do_allocate(
xmin: f64,
xmax: f64,
ymin: f64,
ymax: f64,
nx: usize,
ny: usize,
coords: Vec<(f64, f64)>,
cgl_grid: bool,
) -> Self {
Self {
xmin,
xmax,
ymin,
ymax,
nx,
ny,
npoint: nx * ny,
coords,
nodes_xmin: (0..ny).map(|j| j * nx).collect(),
nodes_xmax: (0..ny).map(|j| j * nx + (nx - 1)).collect(),
nodes_ymin: (0..nx).collect(),
nodes_ymax: (0..nx).map(|i| (ny - 1) * nx + i).collect(),
is_chebyshev_gauss_lobatto: cgl_grid,
}
}
pub fn new(xx: &[f64], yy: &[f64]) -> Result<Self, StrError> {
let nx = xx.len();
let ny = yy.len();
if nx < 2 {
return Err("nx must be ≥ 2");
}
if ny < 2 {
return Err("ny must be ≥ 2");
}
let mut coords = Vec::with_capacity(nx * ny);
let xmin = xx[0];
let xmax = xx[nx - 1];
let ymin = yy[0];
let ymax = yy[ny - 1];
for i in 1..nx {
if xx[i] <= xx[i - 1] {
return Err("xx must be strictly increasing");
}
}
for j in 0..ny {
if j > 0 && yy[j] <= yy[j - 1] {
return Err("yy must be strictly increasing");
}
for i in 0..nx {
coords.push((xx[i], yy[j]));
}
}
Ok(Grid2d::do_allocate(xmin, xmax, ymin, ymax, nx, ny, coords, false))
}
pub fn new_uniform(xmin: f64, xmax: f64, ymin: f64, ymax: f64, nx: usize, ny: usize) -> Result<Self, StrError> {
if nx < 2 {
return Err("nx must be ≥ 2");
}
if ny < 2 {
return Err("ny must be ≥ 2");
}
if xmax <= xmin {
return Err("xmax must be > xmin");
}
if ymax <= ymin {
return Err("ymax must be > ymin");
}
let dx = (xmax - xmin) / ((nx - 1) as f64);
let dy = (ymax - ymin) / ((ny - 1) as f64);
let mut coords = Vec::with_capacity(nx * ny);
for j in 0..ny {
let y = ymin + (j as f64) * dy;
for i in 0..nx {
let x = xmin + (i as f64) * dx;
coords.push((x, y));
}
}
Ok(Grid2d::do_allocate(xmin, xmax, ymin, ymax, nx, ny, coords, false))
}
pub fn new_chebyshev_gauss_lobatto(nx: usize, ny: usize) -> Result<Self, StrError> {
if nx < 2 {
return Err("nx must be ≥ 2");
}
if ny < 2 {
return Err("ny must be ≥ 2");
}
let uu = chebyshev_lobatto_points(nx - 1);
let vv = chebyshev_lobatto_points(ny - 1);
let mut coords = Vec::with_capacity(nx * ny);
for j in 0..ny {
for i in 0..nx {
coords.push((uu[i], vv[j]));
}
}
Ok(Grid2d::do_allocate(-1.0, 1.0, -1.0, 1.0, nx, ny, coords, true))
}
pub fn is_chebyshev_gauss_lobatto(&self) -> bool {
self.is_chebyshev_gauss_lobatto
}
pub fn xmin(&self) -> f64 {
self.xmin
}
pub fn xmax(&self) -> f64 {
self.xmax
}
pub fn ymin(&self) -> f64 {
self.ymin
}
pub fn ymax(&self) -> f64 {
self.ymax
}
pub fn nx(&self) -> usize {
self.nx
}
pub fn ny(&self) -> usize {
self.ny
}
pub fn size(&self) -> usize {
self.npoint
}
pub fn get_m(&self, i: usize, j: usize) -> usize {
i + j * self.nx
}
pub fn get_ij(&self, m: usize) -> (usize, usize) {
let i = m % self.nx;
let j = m / self.nx;
(i, j)
}
pub fn is_xmin(&self, m: usize) -> bool {
m % self.nx == 0 }
pub fn is_xmax(&self, m: usize) -> bool {
m % self.nx == self.nx - 1 }
pub fn is_ymin(&self, m: usize) -> bool {
m / self.nx == 0 }
pub fn is_ymax(&self, m: usize) -> bool {
m / self.nx == self.ny - 1 }
pub fn on_boundary(&self, m: usize) -> bool {
let i = m % self.nx;
let j = m / self.nx;
i == 0 || i == self.nx - 1 || j == 0 || j == self.ny - 1
}
pub fn get_nodes_on_side(&self, side: Side) -> &[usize] {
match side {
Side::Xmin => &self.nodes_xmin,
Side::Xmax => &self.nodes_xmax,
Side::Ymin => &self.nodes_ymin,
Side::Ymax => &self.nodes_ymax,
}
}
pub fn get_boundary_nodes(&self) -> (&[usize], &[usize], &[usize], &[usize]) {
(&self.nodes_xmin, &self.nodes_xmax, &self.nodes_ymin, &self.nodes_ymax)
}
pub fn is_corner(&self, m: usize) -> bool {
if m == 0 || m == self.npoint - 1 {
true
} else {
let i = m % self.nx;
let j = m / self.nx;
(i == 0 && j == self.ny - 1) || (i == self.nx - 1 && j == 0)
}
}
pub fn get_dx_dy(&self) -> Option<(f64, f64)> {
let mut dx = f64::NEG_INFINITY;
let mut dy = f64::NEG_INFINITY;
for j in 1..self.ny {
for i in 1..self.nx {
let m = i + j * self.nx; let l = m - 1; let b = m - self.nx; let (x, y) = self.coords[m];
let (xl, _) = self.coords[l];
let (_, yb) = self.coords[b];
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; }
if dy == f64::NEG_INFINITY {
dy = y - yb;
assert!(dy > 0.0);
} else if f64::abs(y - yb - dy) > 10.0 * f64::EPSILON {
return None; }
}
}
Some((dx, dy))
}
pub fn coord(&self, m: usize) -> (f64, f64) {
self.coords[m]
}
pub fn for_each_coord(&self, mut f: impl FnMut(usize, f64, f64)) {
for (m, (x, y)) in self.coords.iter().enumerate() {
f(m, *x, *y);
}
}
pub fn for_each_node_xmin<F>(&self, mut f: F)
where
F: FnMut(&usize),
{
self.nodes_xmin.iter().for_each(|n| f(n));
}
pub fn for_each_node_xmax<F>(&self, mut f: F)
where
F: FnMut(&usize),
{
self.nodes_xmax.iter().for_each(|n| f(n));
}
pub fn for_each_node_ymin<F>(&self, mut f: F)
where
F: FnMut(&usize),
{
self.nodes_ymin.iter().for_each(|n| f(n));
}
pub fn for_each_node_ymax<F>(&self, mut f: F)
where
F: FnMut(&usize),
{
self.nodes_ymax.iter().for_each(|n| f(n));
}
}
#[cfg(test)]
mod tests {
use super::Grid2d;
use crate::Side;
use russell_lab::approx_eq;
use std::f64::consts::PI;
#[test]
fn new_fails_on_invalid_input() {
assert_eq!(Grid2d::new(&[0.0], &[0.0, 1.0]).err(), Some("nx must be ≥ 2"));
assert_eq!(Grid2d::new(&[0.0, 1.0], &[0.0]).err(), Some("ny must be ≥ 2"));
assert_eq!(
Grid2d::new(&[0.0, 0.0], &[0.0, 1.0]).err(),
Some("xx must be strictly increasing")
);
assert_eq!(
Grid2d::new(&[0.0, 1.0], &[0.0, 0.0]).err(),
Some("yy must be strictly increasing")
);
}
#[test]
fn new_uniform_fails_on_invalid_input() {
assert_eq!(
Grid2d::new_uniform(0.0, 1.0, 0.0, 1.0, 1, 4).err(),
Some("nx must be ≥ 2")
);
assert_eq!(
Grid2d::new_uniform(0.0, 1.0, 0.0, 1.0, 4, 1).err(),
Some("ny must be ≥ 2")
);
assert_eq!(
Grid2d::new_uniform(1.0, 0.0, 0.0, 1.0, 4, 4).err(),
Some("xmax must be > xmin")
);
assert_eq!(
Grid2d::new_uniform(0.0, 1.0, 1.0, 0.0, 4, 4).err(),
Some("ymax must be > ymin")
);
}
#[test]
fn new_chebyshev_gauss_lobatto_fails_on_invalid_input() {
assert_eq!(Grid2d::new_chebyshev_gauss_lobatto(1, 4).err(), Some("nx must be ≥ 2"));
assert_eq!(Grid2d::new_chebyshev_gauss_lobatto(4, 1).err(), Some("ny must be ≥ 2"));
}
#[test]
fn new_works() {
let xx = &[-3.0, -2.9, 2.9, 3.0];
let yy = &[2.0, 5.0, 8.0];
let correct_coords = &[
(-3.0, 2.0), (-2.9, 2.0), (2.9, 2.0), (3.0, 2.0), (-3.0, 5.0), (-2.9, 5.0), (2.9, 5.0), (3.0, 5.0), (-3.0, 8.0), (-2.9, 8.0), (2.9, 8.0), (3.0, 8.0), ];
let grid = Grid2d::new(xx, yy).unwrap();
assert_eq!(grid.xmin, -3.0);
assert_eq!(grid.xmax, 3.0);
assert_eq!(grid.ymin, 2.0);
assert_eq!(grid.ymax, 8.0);
assert_eq!(grid.nx, 4);
assert_eq!(grid.ny, 3);
assert_eq!(grid.coords, correct_coords);
assert_eq!(grid.nodes_xmin, &[0, 4, 8]);
assert_eq!(grid.nodes_xmax, &[3, 7, 11]);
assert_eq!(grid.nodes_ymin, &[0, 1, 2, 3]);
assert_eq!(grid.nodes_ymax, &[8, 9, 10, 11]);
assert_eq!(grid.nx(), 4);
assert_eq!(grid.ny(), 3);
assert_eq!(grid.size(), 12);
assert_eq!(grid.get_dx_dy(), None);
let mut coords = Vec::new();
grid.for_each_coord(|_m, x, y| coords.push((x, y)));
assert_eq!(coords, correct_coords);
assert_eq!(grid.xmin(), -3.0);
assert_eq!(grid.xmax(), 3.0);
assert_eq!(grid.ymin(), 2.0);
assert_eq!(grid.ymax(), 8.0);
}
#[test]
fn new_uniform_works() {
let xmin = -3.0;
let xmax = 3.0;
let ymin = 2.0;
let ymax = 8.0;
let nx = 4;
let ny = 3;
let correct_coords = &[
(-3.0, 2.0), (-1.0, 2.0), (1.0, 2.0), (3.0, 2.0), (-3.0, 5.0), (-1.0, 5.0), (1.0, 5.0), (3.0, 5.0), (-3.0, 8.0), (-1.0, 8.0), (1.0, 8.0), (3.0, 8.0), ];
let grid = Grid2d::new_uniform(xmin, xmax, ymin, ymax, nx, ny).unwrap();
assert_eq!(grid.xmin, -3.0);
assert_eq!(grid.xmax, 3.0);
assert_eq!(grid.ymin, 2.0);
assert_eq!(grid.ymax, 8.0);
assert_eq!(grid.nx, 4);
assert_eq!(grid.ny, 3);
assert_eq!(grid.coords, correct_coords);
assert_eq!(grid.nodes_xmin, &[0, 4, 8]);
assert_eq!(grid.nodes_xmax, &[3, 7, 11]);
assert_eq!(grid.nodes_ymin, &[0, 1, 2, 3]);
assert_eq!(grid.nodes_ymax, &[8, 9, 10, 11]);
assert_eq!(grid.nx(), 4);
assert_eq!(grid.ny(), 3);
assert_eq!(grid.size(), 12);
assert_eq!(grid.get_dx_dy(), Some((2.0, 3.0)));
let mut coords = Vec::new();
grid.for_each_coord(|_m, x, y| coords.push((x, y)));
assert_eq!(coords, correct_coords);
let mut left = Vec::new();
let mut right = Vec::new();
let mut bottom = Vec::new();
let mut top = Vec::new();
let mut xx_min = Vec::new();
let mut xx_max = Vec::new();
let mut yy_min = Vec::new();
let mut yy_max = Vec::new();
grid.for_each_node_xmin(|n| {
left.push(*n);
let (x, _y) = grid.coord(*n);
xx_min.push(x);
});
grid.for_each_node_xmax(|n| {
right.push(*n);
let (x, _y) = grid.coord(*n);
xx_max.push(x);
});
grid.for_each_node_ymin(|n| {
bottom.push(*n);
let (_x, y) = grid.coord(*n);
yy_min.push(y);
});
grid.for_each_node_ymax(|n| {
top.push(*n);
let (_x, y) = grid.coord(*n);
yy_max.push(y);
});
assert_eq!(left, &[0, 4, 8]);
assert_eq!(right, &[3, 7, 11]);
assert_eq!(bottom, &[0, 1, 2, 3]);
assert_eq!(top, &[8, 9, 10, 11]);
assert_eq!(xx_min, &[-3.0, -3.0, -3.0]);
assert_eq!(xx_max, &[3.0, 3.0, 3.0]);
assert_eq!(yy_min, &[2.0, 2.0, 2.0, 2.0]);
assert_eq!(yy_max, &[8.0, 8.0, 8.0, 8.0]);
}
#[test]
fn new_chebyshev_gauss_lobatto_works() {
let nx = 4;
let ny = 3;
let um1 = -f64::cos(PI / 3.0); let um2 = -f64::cos(2.0 * PI / 3.0);
let correct_coords = &[
(-1.0, -1.0), (um1, -1.0), (um2, -1.0), (1.0, -1.0), (-1.0, 0.0), (um1, 0.0), (um2, 0.0), (1.0, 0.0), (-1.0, 1.0), (um1, 1.0), (um2, 1.0), (1.0, 1.0), ];
let grid = Grid2d::new_chebyshev_gauss_lobatto(nx, ny).unwrap();
assert_eq!(grid.xmin, -1.0);
assert_eq!(grid.xmax, 1.0);
assert_eq!(grid.ymin, -1.0);
assert_eq!(grid.ymax, 1.0);
assert_eq!(grid.nx, 4);
assert_eq!(grid.ny, 3);
assert_eq!(grid.nodes_xmin, &[0, 4, 8]);
assert_eq!(grid.nodes_xmax, &[3, 7, 11]);
assert_eq!(grid.nodes_ymin, &[0, 1, 2, 3]);
assert_eq!(grid.nodes_ymax, &[8, 9, 10, 11]);
for (m, &(x, y)) in correct_coords.iter().enumerate() {
let (xg, yg) = grid.coord(m);
approx_eq(x, xg, 1e-15);
approx_eq(y, yg, 1e-15);
}
}
#[test]
fn get_dx_dy_works_31x31() {
let (nx, ny) = (31, 31);
let grid = Grid2d::new_uniform(0.0, 3.0, 0.0, 3.0, nx, ny).unwrap();
assert_eq!(grid.get_dx_dy(), Some((0.1, 0.1)));
}
#[test]
fn get_dx_dy_captures_non_uniform_levels() {
let mut grid = Grid2d::new_uniform(-3.0, 3.0, 2.0, 8.0, 4, 3).unwrap();
assert_eq!(grid.nx, 4);
assert_eq!(grid.ny, 3);
assert_eq!(grid.get_dx_dy(), Some((2.0, 3.0)));
assert_eq!(grid.coords[6], (1.0, 5.0)); grid.coords[6] = (1.1, 5.0); assert_eq!(grid.get_dx_dy(), None);
grid.coords[6] = (1.0, 5.0); assert_eq!(grid.get_dx_dy(), Some((2.0, 3.0)));
grid.coords[6] = (1.0, 5.1); assert_eq!(grid.get_dx_dy(), None);
}
#[test]
fn get_dx_dy_uniform_grids() {
let grid = Grid2d::new_uniform(0.0, 6.0, 0.0, 4.0, 4, 3).unwrap();
assert_eq!(grid.get_dx_dy(), Some((2.0, 2.0)));
let grid = Grid2d::new_uniform(0.0, 1.0, 0.0, 1.0, 5, 3).unwrap();
assert_eq!(grid.get_dx_dy(), Some((0.25, 0.5)));
let grid = Grid2d::new_uniform(-2.0, 2.0, -1.0, 3.0, 3, 5).unwrap();
assert_eq!(grid.get_dx_dy(), Some((2.0, 1.0)));
let grid = Grid2d::new_uniform(0.0, 1.0, 0.0, 1.0, 2, 2).unwrap();
assert_eq!(grid.get_dx_dy(), Some((1.0, 1.0)));
}
#[test]
fn get_dx_dy_non_uniform_grids() {
let xx = &[0.0, 0.1, 0.5, 1.0]; let yy = &[0.0, 0.5, 1.0]; let grid = Grid2d::new(xx, yy).unwrap();
assert_eq!(grid.get_dx_dy(), None);
let xx = &[0.0, 1.0, 2.0]; let yy = &[0.0, 0.1, 1.0]; let grid = Grid2d::new(xx, yy).unwrap();
assert_eq!(grid.get_dx_dy(), None);
let xx = &[0.0, 0.2, 0.7, 1.0]; let yy = &[0.0, 0.3, 0.8, 1.0]; let grid = Grid2d::new(xx, yy).unwrap();
assert_eq!(grid.get_dx_dy(), None);
let xx = &[0.1, 1.0, 10.0, 100.0]; let yy = &[0.01, 0.1, 1.0]; let grid = Grid2d::new(xx, yy).unwrap();
assert_eq!(grid.get_dx_dy(), None);
}
#[test]
fn get_dx_dy_uniform_from_arrays() {
let xx = &[0.0, 1.0, 2.0, 3.0, 4.0]; let yy = &[0.0, 0.5, 1.0]; let grid = Grid2d::new(xx, yy).unwrap();
assert_eq!(grid.get_dx_dy(), Some((1.0, 0.5)));
let xx = &[-2.0, -1.0, 0.0, 1.0]; let yy = &[-1.0, 1.0, 3.0]; let grid = Grid2d::new(xx, yy).unwrap();
assert_eq!(grid.get_dx_dy(), Some((1.0, 2.0)));
let xx = &[0.0, 0.25, 0.5, 0.75, 1.0]; let yy = &[0.0, 1.0 / 3.0, 2.0 / 3.0, 1.0]; let grid = Grid2d::new(xx, yy).unwrap();
let result = grid.get_dx_dy().unwrap();
assert!((result.0 - 0.25).abs() < 1e-15);
assert!((result.1 - 1.0 / 3.0).abs() < 1e-15);
}
#[test]
fn get_dx_dy_precision_edge_cases() {
let grid = Grid2d::new_uniform(0.0, 1e-6, 0.0, 1e-6, 3, 3).unwrap();
let result = grid.get_dx_dy().unwrap();
assert!((result.0 - 5e-7).abs() < 1e-21); assert!((result.1 - 5e-7).abs() < 1e-21);
let grid = Grid2d::new_uniform(0.0, 1e6, 0.0, 1e6, 3, 3).unwrap();
let result = grid.get_dx_dy().unwrap();
assert!((result.0 - 5e5).abs() < 1e-9); assert!((result.1 - 5e5).abs() < 1e-9);
let mut xx = vec![0.0, 1.0, 2.0, 3.0];
xx[2] += 11.0 * f64::EPSILON; let yy = &[0.0, 1.0, 2.0];
let grid = Grid2d::new(&xx, yy).unwrap();
assert_eq!(grid.get_dx_dy(), None);
let mut xx = vec![0.0, 1.0, 2.0, 3.0];
xx[2] += f64::EPSILON / 2.0; let yy = &[0.0, 1.0, 2.0];
let grid = Grid2d::new(&xx, yy).unwrap();
assert_eq!(grid.get_dx_dy(), Some((1.0, 1.0))); }
#[test]
fn get_dx_dy_different_grid_sizes() {
let grid = Grid2d::new_uniform(0.0, 3.0, 0.0, 4.0, 2, 2).unwrap();
assert_eq!(grid.get_dx_dy(), Some((3.0, 4.0)));
let grid = Grid2d::new_uniform(0.0, 9.0, 0.0, 1.0, 10, 2).unwrap();
assert_eq!(grid.get_dx_dy(), Some((1.0, 1.0)));
let grid = Grid2d::new_uniform(0.0, 1.0, 0.0, 9.0, 2, 10).unwrap();
assert_eq!(grid.get_dx_dy(), Some((1.0, 1.0)));
let grid = Grid2d::new_uniform(0.0, 1.0, 0.0, 1.0, 50, 50).unwrap();
let result = grid.get_dx_dy().unwrap();
assert!((result.0 - 1.0 / 49.0).abs() < 1e-15);
assert!((result.1 - 1.0 / 49.0).abs() < 1e-15);
}
#[test]
fn get_dx_dy_boundary_coordinates() {
let grid = Grid2d::new_uniform(-1.0, 1.0, -2.0, 2.0, 3, 5).unwrap();
assert_eq!(grid.get_dx_dy(), Some((1.0, 1.0)));
let grid = Grid2d::new_uniform(0.0, 1e-10, 0.0, 1e-10, 2, 2).unwrap();
let result = grid.get_dx_dy().unwrap();
assert!((result.0 - 1e-10).abs() < 1e-25);
assert!((result.1 - 1e-10).abs() < 1e-25);
let grid = Grid2d::new_uniform(1e6, 1e6 + 4.0, 1e9, 1e9 + 6.0, 3, 4).unwrap();
assert_eq!(grid.get_dx_dy(), Some((2.0, 2.0)));
}
#[test]
fn get_m_and_get_ij_work() {
let grid = Grid2d::new_uniform(0.0, 1.0, 0.0, 1.0, 3, 3).unwrap();
assert_eq!(grid.get_m(0, 0), 0);
assert_eq!(grid.get_m(1, 0), 1);
assert_eq!(grid.get_m(2, 0), 2);
assert_eq!(grid.get_m(0, 1), 3);
assert_eq!(grid.get_m(1, 1), 4);
assert_eq!(grid.get_m(2, 1), 5);
assert_eq!(grid.get_m(0, 2), 6);
assert_eq!(grid.get_m(1, 2), 7);
assert_eq!(grid.get_m(2, 2), 8);
assert_eq!(grid.get_ij(0), (0, 0));
assert_eq!(grid.get_ij(1), (1, 0));
assert_eq!(grid.get_ij(2), (2, 0));
assert_eq!(grid.get_ij(3), (0, 1));
assert_eq!(grid.get_ij(4), (1, 1));
assert_eq!(grid.get_ij(5), (2, 1));
assert_eq!(grid.get_ij(6), (0, 2));
assert_eq!(grid.get_ij(7), (1, 2));
assert_eq!(grid.get_ij(8), (2, 2));
}
#[test]
fn is_chebyshev_gauss_lobatto_works() {
let grid = Grid2d::new_uniform(0.0, 1.0, 0.0, 1.0, 2, 2).unwrap();
assert_eq!(grid.is_chebyshev_gauss_lobatto(), false);
let grid = Grid2d::new_chebyshev_gauss_lobatto(2, 2).unwrap();
assert_eq!(grid.is_chebyshev_gauss_lobatto(), true);
}
#[test]
fn boundary_methods_work() {
let grid = Grid2d::new_uniform(0.0, 3.0, 0.0, 2.0, 4, 3).unwrap();
assert!(grid.is_xmin(0));
assert!(grid.is_xmin(4));
assert!(grid.is_xmin(8));
assert!(!grid.is_xmin(1));
assert!(grid.is_xmax(3));
assert!(grid.is_xmax(7));
assert!(grid.is_xmax(11));
assert!(!grid.is_xmax(2));
assert!(grid.is_ymin(0));
assert!(grid.is_ymin(1));
assert!(grid.is_ymin(2));
assert!(grid.is_ymin(3));
assert!(!grid.is_ymin(4));
assert!(grid.is_ymax(8));
assert!(grid.is_ymax(9));
assert!(grid.is_ymax(10));
assert!(grid.is_ymax(11));
assert!(!grid.is_ymax(7));
assert!(grid.on_boundary(0));
assert!(grid.on_boundary(1));
assert!(grid.on_boundary(3));
assert!(grid.on_boundary(4));
assert!(grid.on_boundary(7));
assert!(grid.on_boundary(8));
assert!(grid.on_boundary(11));
assert!(!grid.on_boundary(5));
assert!(!grid.on_boundary(6));
assert_eq!(grid.get_nodes_on_side(Side::Xmin), &[0, 4, 8]);
assert_eq!(grid.get_nodes_on_side(Side::Xmax), &[3, 7, 11]);
assert_eq!(grid.get_nodes_on_side(Side::Ymin), &[0, 1, 2, 3]);
assert_eq!(grid.get_nodes_on_side(Side::Ymax), &[8, 9, 10, 11]);
let (l, r, b, t) = grid.get_boundary_nodes();
assert_eq!(l, &[0, 4, 8]);
assert_eq!(r, &[3, 7, 11]);
assert_eq!(b, &[0, 1, 2, 3]);
assert_eq!(t, &[8, 9, 10, 11]);
}
#[test]
fn is_corner_works() {
let grid = Grid2d::new_uniform(0.0, 3.0, 0.0, 2.0, 4, 3).unwrap();
assert!(grid.is_corner(0));
assert!(grid.is_corner(3));
assert!(grid.is_corner(8));
assert!(grid.is_corner(11));
assert!(!grid.is_corner(1));
assert!(!grid.is_corner(2));
assert!(!grid.is_corner(4));
assert!(!grid.is_corner(5));
assert!(!grid.is_corner(6));
assert!(!grid.is_corner(7));
assert!(!grid.is_corner(9));
assert!(!grid.is_corner(10));
}
}