#![deny(rustdoc::broken_intra_doc_links)]
use crate::scalars::{NumIntervals, PositiveIntTrait, PositiveNumPoints1D};
use duplicate::duplicate_item;
use serde::{Deserialize, Serialize};
use try_create::TryNew;
pub(crate) mod sealed {
use crate::intervals::{IntervalLowerClosedUpperOpen, IntervalLowerOpenUpperClosed};
use num_valid::RealScalar;
pub trait SupportsCircularTopology {}
impl<T: RealScalar> SupportsCircularTopology for IntervalLowerClosedUpperOpen<T> {}
impl<T: RealScalar> SupportsCircularTopology for IntervalLowerOpenUpperClosed<T> {}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Topology1D {
RealLine,
Circle,
}
impl Topology1D {
pub fn is_periodic(&self) -> bool {
matches!(self, Topology1D::Circle)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Side {
Left,
Right,
}
impl Side {
#[inline(always)]
pub fn is_left(&self) -> bool {
matches!(&self, Side::Left)
}
#[inline(always)]
pub fn is_right(&self) -> bool {
matches!(&self, Side::Right)
}
}
pub trait HasSide: Copy + Clone + std::fmt::Debug + PartialEq + Eq {
fn side(&self) -> Side;
}
impl HasSide for Side {
#[inline(always)]
fn side(&self) -> Side {
*self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IntervalSide {
pub interval_index: usize,
pub side: Side,
}
impl IntervalSide {
#[inline(always)]
pub fn new(interval_index: usize, side: Side) -> Self {
Self {
interval_index,
side,
}
}
#[inline(always)]
pub fn coord_index(&self) -> usize {
match self.side {
Side::Left => self.interval_index,
Side::Right => self.interval_index + 1,
}
}
#[inline(always)]
pub fn interval_index(&self) -> usize {
self.interval_index
}
}
impl HasSide for IntervalSide {
#[inline(always)]
fn side(&self) -> Side {
self.side
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Neighbors1D {
left: Option<usize>,
right: Option<usize>,
}
impl Neighbors1D {
pub fn left(&self) -> Option<usize> {
self.left
}
pub fn right(&self) -> Option<usize> {
self.right
}
}
pub trait IndexSpace1D {
type Counter: PositiveIntTrait;
fn topology(&self) -> &Topology1D;
fn count(&self) -> &Self::Counter;
}
pub trait Adjacency1D: IndexSpace1D {
fn neighbor(&self, id: &usize, side: &Side) -> Option<usize> {
match side {
Side::Left => self.left_neighbor(id),
Side::Right => self.right_neighbor(id),
}
}
fn left_neighbor(&self, id: &usize) -> Option<usize> {
let n = *self.count().as_ref();
more_asserts::debug_assert_lt!(*id, n, "ID {} out of bounds for count {}", id, n);
if *id == 0 {
match self.topology() {
Topology1D::RealLine => None,
Topology1D::Circle => Some(n - 1),
}
} else {
Some(id - 1)
}
}
fn right_neighbor(&self, id: &usize) -> Option<usize> {
let n = *self.count().as_ref();
more_asserts::debug_assert_lt!(*id, n, "ID {} out of bounds for count {}", id, n);
if *id == n - 1 {
match self.topology() {
Topology1D::RealLine => None,
Topology1D::Circle => Some(0),
}
} else {
Some(id + 1)
}
}
fn neighbors(&self, id: &usize) -> Neighbors1D {
Neighbors1D {
left: self.left_neighbor(id),
right: self.right_neighbor(id),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct PointIndexSpace1D {
topology: Topology1D,
count: PositiveNumPoints1D,
}
impl PointIndexSpace1D {
pub fn new(topology: Topology1D, count: PositiveNumPoints1D) -> Self {
Self { topology, count }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntervalIndexSpace1D {
topology: Topology1D,
count: NumIntervals,
}
impl IntervalIndexSpace1D {
pub fn new(topology: Topology1D, count: NumIntervals) -> Self {
Self { topology, count }
}
}
#[duplicate_item(
T counter;
[PointIndexSpace1D] [PositiveNumPoints1D];
[IntervalIndexSpace1D] [NumIntervals];
)]
impl IndexSpace1D for T {
type Counter = counter;
fn topology(&self) -> &Topology1D {
&self.topology
}
fn count(&self) -> &Self::Counter {
&self.count
}
}
impl Adjacency1D for PointIndexSpace1D {}
impl Adjacency1D for IntervalIndexSpace1D {}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Grid1DIndexSpaces {
interval_index_space: IntervalIndexSpace1D,
point_index_space: PointIndexSpace1D,
}
impl Grid1DIndexSpaces {
pub(crate) fn new(num_intervals: NumIntervals, topology: Topology1D) -> Self {
let interval_index_space = IntervalIndexSpace1D::new(topology, num_intervals);
let point_index_space = PointIndexSpace1D::new(
topology,
PositiveNumPoints1D::try_new(*num_intervals.as_ref() + 1).unwrap(),
);
Self {
interval_index_space,
point_index_space,
}
}
pub fn interval_index_space(&self) -> &IntervalIndexSpace1D {
&self.interval_index_space
}
pub fn point_index_space(&self) -> &PointIndexSpace1D {
&self.point_index_space
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalars::NumIntervals;
use try_create::TryNew;
fn point_space(topology: Topology1D, n: usize) -> PointIndexSpace1D {
PointIndexSpace1D {
topology,
count: PositiveNumPoints1D::try_new(n).unwrap(),
}
}
fn interval_space(topology: Topology1D, n: usize) -> IntervalIndexSpace1D {
IntervalIndexSpace1D {
topology,
count: NumIntervals::try_new(n).unwrap(),
}
}
mod side {
use super::*;
#[test]
fn side_equality() {
assert_eq!(Side::Left, Side::Left);
assert_eq!(Side::Right, Side::Right);
assert_ne!(Side::Right, Side::Left);
}
#[test]
fn is_left() {
assert!(Side::Left.is_left());
assert!(!Side::Right.is_left());
}
#[test]
fn is_right() {
assert!(Side::Right.is_right());
assert!(!Side::Left.is_right());
}
#[test]
fn has_side_for_side_returns_self() {
assert_eq!(Side::Left.side(), Side::Left);
assert_eq!(Side::Right.side(), Side::Right);
}
#[test]
fn left_point_index() {
let data_3 = IntervalSide::new(3, Side::Left);
assert_eq!(data_3.coord_index(), 3);
let data_0 = IntervalSide::new(0, Side::Left);
assert_eq!(data_0.coord_index(), 0);
}
#[test]
fn right_point_index() {
let data_3 = IntervalSide::new(3, Side::Right);
assert_eq!(data_3.coord_index(), 4);
let data_0 = IntervalSide::new(0, Side::Right);
assert_eq!(data_0.coord_index(), 1);
}
#[test]
fn interval_side_interval_index_getter() {
let s = IntervalSide::new(7, Side::Left);
assert_eq!(s.interval_index(), 7);
let s2 = IntervalSide::new(0, Side::Right);
assert_eq!(s2.interval_index(), 0);
}
#[test]
fn has_side_for_interval_side() {
let left = IntervalSide::new(2, Side::Left);
assert_eq!(left.side(), Side::Left);
let right = IntervalSide::new(5, Side::Right);
assert_eq!(right.side(), Side::Right);
}
}
mod topology {
use super::*;
#[test]
fn topology1d_real_line_eq_self() {
assert_eq!(Topology1D::RealLine, Topology1D::RealLine);
}
#[test]
fn topology1d_circle_eq_self() {
assert_eq!(Topology1D::Circle, Topology1D::Circle);
}
#[test]
fn topology1d_real_line_ne_circle() {
assert_ne!(Topology1D::RealLine, Topology1D::Circle);
}
#[test]
fn topology1d_copy() {
let t = Topology1D::Circle;
let t2 = t; assert_eq!(t, t2);
}
#[test]
fn is_periodic_real_line_is_false() {
assert!(!Topology1D::RealLine.is_periodic());
}
#[test]
fn is_periodic_circle_is_true() {
assert!(Topology1D::Circle.is_periodic());
}
}
mod neighbors1d {
use super::*;
#[test]
fn neighbors1d_construction_and_eq() {
let a = Neighbors1D {
left: Some(0),
right: Some(2),
};
let b = Neighbors1D {
left: Some(0),
right: Some(2),
};
assert_eq!(a, b);
}
#[test]
fn neighbors1d_none_variants() {
let a = Neighbors1D {
left: None,
right: None,
};
assert_eq!(
a,
Neighbors1D {
left: None,
right: None
}
);
}
#[test]
fn neighbors1d_copy() {
let a = Neighbors1D {
left: Some(1),
right: None,
};
let b = a; assert_eq!(a, b);
}
}
#[test]
fn point_index_space_topology_and_count() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(*s.topology(), Topology1D::RealLine);
assert_eq!(*s.count().as_ref(), 5);
}
#[test]
fn interval_index_space_topology_and_count() {
let s = interval_space(Topology1D::Circle, 4);
assert_eq!(*s.topology(), Topology1D::Circle);
assert_eq!(*s.count().as_ref(), 4);
}
#[test]
fn left_neighbor_open_first_element_is_none() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(s.left_neighbor(&0), None);
}
#[test]
fn left_neighbor_open_middle() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(s.left_neighbor(&3), Some(2));
}
#[test]
fn left_neighbor_open_last_element() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(s.left_neighbor(&4), Some(3));
}
#[test]
fn left_neighbor_periodic_first_element_wraps() {
let s = interval_space(Topology1D::Circle, 5);
assert_eq!(s.left_neighbor(&0), Some(4));
}
#[test]
fn left_neighbor_periodic_middle() {
let s = interval_space(Topology1D::Circle, 5);
assert_eq!(s.left_neighbor(&2), Some(1));
}
#[test]
fn left_neighbor_periodic_last_element() {
let s = interval_space(Topology1D::Circle, 5);
assert_eq!(s.left_neighbor(&4), Some(3));
}
#[test]
fn right_neighbor_open_first_element() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(s.right_neighbor(&0), Some(1));
}
#[test]
fn right_neighbor_open_middle() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(s.right_neighbor(&2), Some(3));
}
#[test]
fn right_neighbor_open_last_element_is_none() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(s.right_neighbor(&4), None);
}
#[test]
fn right_neighbor_periodic_first_element() {
let s = interval_space(Topology1D::Circle, 5);
assert_eq!(s.right_neighbor(&0), Some(1));
}
#[test]
fn right_neighbor_periodic_middle() {
let s = interval_space(Topology1D::Circle, 5);
assert_eq!(s.right_neighbor(&2), Some(3));
}
#[test]
fn right_neighbor_periodic_last_element_wraps() {
let s = interval_space(Topology1D::Circle, 5);
assert_eq!(s.right_neighbor(&4), Some(0));
}
#[test]
fn neighbors_open_interior() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(
s.neighbors(&2),
Neighbors1D {
left: Some(1),
right: Some(3)
}
);
}
#[test]
fn neighbors_open_left_boundary() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(
s.neighbors(&0),
Neighbors1D {
left: None,
right: Some(1)
}
);
}
#[test]
fn neighbors_open_right_boundary() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(
s.neighbors(&4),
Neighbors1D {
left: Some(3),
right: None
}
);
}
#[test]
fn neighbors_periodic_wraps_at_boundaries() {
let s = interval_space(Topology1D::Circle, 5);
assert_eq!(
s.neighbors(&0),
Neighbors1D {
left: Some(4),
right: Some(1)
}
);
assert_eq!(
s.neighbors(&4),
Neighbors1D {
left: Some(3),
right: Some(0)
}
);
}
#[test]
fn neighbors_open_single_element_both_none() {
let s = point_space(Topology1D::RealLine, 1);
assert_eq!(
s.neighbors(&0),
Neighbors1D {
left: None,
right: None
}
);
}
#[test]
fn neighbors_periodic_single_element_wraps_to_itself() {
let s = interval_space(Topology1D::Circle, 1);
assert_eq!(
s.neighbors(&0),
Neighbors1D {
left: Some(0),
right: Some(0)
}
);
}
#[test]
fn neighbor_dispatch_left_and_right_open() {
let s = point_space(Topology1D::RealLine, 5);
assert_eq!(s.neighbor(&2, &Side::Left), s.left_neighbor(&2));
assert_eq!(s.neighbor(&2, &Side::Right), s.right_neighbor(&2));
assert_eq!(s.neighbor(&0, &Side::Left), None);
assert_eq!(s.neighbor(&4, &Side::Right), None);
}
#[test]
fn neighbor_dispatch_left_and_right_periodic() {
let s = interval_space(Topology1D::Circle, 4);
assert_eq!(s.neighbor(&0, &Side::Left), Some(3)); assert_eq!(s.neighbor(&0, &Side::Right), Some(1));
assert_eq!(s.neighbor(&3, &Side::Right), Some(0)); }
#[test]
fn point_index_space_new_constructor() {
let count = PositiveNumPoints1D::try_new(7).unwrap();
let s = PointIndexSpace1D::new(Topology1D::Circle, count);
assert_eq!(*s.topology(), Topology1D::Circle);
assert_eq!(*s.count().as_ref(), 7);
}
#[test]
fn interval_index_space_new_constructor() {
let count = NumIntervals::try_new(3).unwrap();
let s = IntervalIndexSpace1D::new(Topology1D::RealLine, count);
assert_eq!(*s.topology(), Topology1D::RealLine);
assert_eq!(*s.count().as_ref(), 3);
}
mod grid1d_index_spaces {
use super::*;
fn make(n: usize, topology: Topology1D) -> Grid1DIndexSpaces {
Grid1DIndexSpaces::new(NumIntervals::try_new(n).unwrap(), topology)
}
#[test]
fn new_sets_correct_interval_count() {
let g = make(4, Topology1D::RealLine);
assert_eq!(*g.interval_index_space().count().as_ref(), 4);
}
#[test]
fn new_sets_point_count_as_n_plus_1() {
let g = make(4, Topology1D::RealLine);
assert_eq!(*g.point_index_space().count().as_ref(), 5);
}
#[test]
fn topology_is_propagated_to_both_spaces() {
let g = make(3, Topology1D::Circle);
assert_eq!(*g.interval_index_space().topology(), Topology1D::Circle);
assert_eq!(*g.point_index_space().topology(), Topology1D::Circle);
}
#[test]
fn interval_index_space_adjacency_uses_topology() {
let g = make(4, Topology1D::Circle);
assert_eq!(g.interval_index_space().right_neighbor(&3), Some(0));
}
#[test]
fn point_index_space_adjacency_uses_topology() {
let g = make(4, Topology1D::RealLine);
assert_eq!(g.point_index_space().left_neighbor(&0), None);
assert_eq!(g.point_index_space().right_neighbor(&4), None);
}
#[test]
fn clone_and_equality() {
let g1 = make(5, Topology1D::Circle);
let g2 = g1.clone();
assert_eq!(g1, g2);
}
}
#[test]
#[cfg(debug_assertions)]
#[should_panic]
fn left_neighbor_panics_on_out_of_bounds_open() {
let s = point_space(Topology1D::RealLine, 5);
let _ = s.left_neighbor(&5); }
#[test]
#[cfg(debug_assertions)]
#[should_panic]
fn left_neighbor_panics_on_out_of_bounds_periodic() {
let s = interval_space(Topology1D::Circle, 4);
let _ = s.left_neighbor(&4); }
#[test]
#[cfg(debug_assertions)]
#[should_panic]
fn left_neighbor_panics_on_far_out_of_bounds() {
let s = point_space(Topology1D::RealLine, 3);
let _ = s.left_neighbor(&100);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic]
fn right_neighbor_panics_on_out_of_bounds_open() {
let s = point_space(Topology1D::RealLine, 5);
let _ = s.right_neighbor(&5); }
#[test]
#[cfg(debug_assertions)]
#[should_panic]
fn right_neighbor_panics_on_out_of_bounds_periodic() {
let s = interval_space(Topology1D::Circle, 4);
let _ = s.right_neighbor(&4); }
#[test]
#[cfg(debug_assertions)]
#[should_panic]
fn right_neighbor_panics_on_far_out_of_bounds() {
let s = point_space(Topology1D::RealLine, 3);
let _ = s.right_neighbor(&100);
}
}