use crate::types::{Integer, Size};
use super::fdmlinearopiterator::FdmLinearOpIterator;
#[derive(Clone, Debug)]
pub struct FdmLinearOpLayout {
size: Size,
dim: Vec<Size>,
spacing: Vec<Size>,
}
impl FdmLinearOpLayout {
pub fn new(dim: Vec<Size>) -> Self {
assert!(!dim.is_empty(), "layout needs at least one dimension");
let mut spacing = Vec::with_capacity(dim.len());
let mut stride = 1;
for extent in &dim {
spacing.push(stride);
stride *= extent;
}
FdmLinearOpLayout {
size: stride,
dim,
spacing,
}
}
pub fn dim(&self) -> &[Size] {
&self.dim
}
pub fn spacing(&self) -> &[Size] {
&self.spacing
}
pub fn size(&self) -> Size {
self.size
}
pub fn index(&self, coordinates: &[Size]) -> Size {
assert_eq!(
coordinates.len(),
self.dim.len(),
"coordinate count mismatch"
);
coordinates
.iter()
.zip(&self.spacing)
.map(|(c, s)| c * s)
.sum()
}
pub fn begin(&self) -> FdmLinearOpIterator {
FdmLinearOpIterator::new(self.dim.clone())
}
pub fn iter(&self) -> impl Iterator<Item = FdmLinearOpIterator> + '_ {
let mut current = self.begin();
std::iter::from_fn(move || {
if current.index() >= self.size {
return None;
}
let position = current.clone();
current.advance();
Some(position)
})
}
pub fn neighbourhood(
&self,
iterator: &FdmLinearOpIterator,
direction: Size,
offset: Integer,
) -> Size {
let base = iterator.index() - iterator.coordinates()[direction] * self.spacing[direction];
base + self.reflect(iterator, direction, offset) * self.spacing[direction]
}
pub fn neighbourhood2(
&self,
iterator: &FdmLinearOpIterator,
direction1: Size,
offset1: Integer,
direction2: Size,
offset2: Integer,
) -> Size {
let base = iterator.index()
- iterator.coordinates()[direction1] * self.spacing[direction1]
- iterator.coordinates()[direction2] * self.spacing[direction2];
base + self.reflect(iterator, direction1, offset1) * self.spacing[direction1]
+ self.reflect(iterator, direction2, offset2) * self.spacing[direction2]
}
pub fn iter_neighbourhood(
&self,
iterator: &FdmLinearOpIterator,
direction: Size,
offset: Integer,
) -> FdmLinearOpIterator {
let mut coordinates = iterator.coordinates().to_vec();
coordinates[direction] = self.reflect(iterator, direction, offset);
let index = self.index(&coordinates);
FdmLinearOpIterator::with_coordinates(self.dim.clone(), coordinates, index)
}
fn reflect(&self, iterator: &FdmLinearOpIterator, direction: Size, offset: Integer) -> Size {
let extent = self.dim[direction] as i64;
let mut coordinate = iterator.coordinates()[direction] as i64 + offset as i64;
if coordinate < 0 {
coordinate = -coordinate;
} else if coordinate >= extent {
coordinate = 2 * (extent - 1) - coordinate;
}
coordinate as Size
}
}
#[cfg(test)]
mod tests {
use super::*;
const DIM: [Size; 3] = [5, 7, 8];
fn layout() -> FdmLinearOpLayout {
FdmLinearOpLayout::new(DIM.to_vec())
}
#[test]
fn dimensions_size_and_spacing() {
let layout = layout();
assert_eq!(layout.dim().len(), DIM.len());
assert_eq!(layout.size(), DIM.iter().product::<Size>());
assert_eq!(layout.size(), 280);
assert_eq!(layout.spacing(), [1, 5, 35]);
}
#[test]
fn index_is_row_major() {
let layout = layout();
for k in 0..DIM[0] {
for l in 0..DIM[1] {
for m in 0..DIM[2] {
let expected = k + l * DIM[0] + m * DIM[0] * DIM[1];
assert_eq!(layout.index(&[k, l, m]), expected);
}
}
}
}
#[test]
fn neighbourhood_reflects_at_the_boundaries() {
let layout = layout();
let mut iter = layout.begin();
for m in 0..DIM[2] {
for l in 0..DIM[1] {
for k in 0..DIM[0] {
assert_eq!(iter.coordinates(), [k, l, m]);
for n in 1..4 {
let reflected = if l < DIM[1] - n {
l + n
} else {
DIM[1] - 1 - (l + n - (DIM[1] - 1))
};
let expected = k + m * DIM[0] * DIM[1] + reflected * DIM[0];
assert_eq!(layout.neighbourhood(&iter, 1, n as Integer), expected);
}
for n in 1..7 {
let reflected = m.abs_diff(n);
let expected = k + l * DIM[0] + reflected * DIM[0] * DIM[1];
assert_eq!(layout.neighbourhood(&iter, 2, -(n as Integer)), expected);
}
iter.advance();
}
}
}
}
#[test]
fn iter_visits_every_point_in_order() {
let layout = layout();
let visited: Vec<_> = layout.iter().collect();
assert_eq!(visited.len(), layout.size());
for (expected_index, position) in visited.iter().enumerate() {
assert_eq!(position.index(), expected_index);
assert_eq!(layout.index(position.coordinates()), expected_index);
}
}
#[test]
fn iter_neighbourhood_agrees_with_neighbourhood() {
let layout = layout();
for position in layout.iter() {
for direction in 0..DIM.len() {
for offset in -3..=3 {
let neighbour = layout.iter_neighbourhood(&position, direction, offset);
assert_eq!(
neighbour.index(),
layout.neighbourhood(&position, direction, offset)
);
}
}
}
}
#[test]
fn neighbourhood2_offsets_both_directions() {
let layout = layout();
let origin = layout.begin();
assert_eq!(layout.neighbourhood(&origin, 1, 3), 15);
assert_eq!(layout.neighbourhood2(&origin, 0, 2, 2, 3), 2 + 3 * 35);
assert_eq!(layout.neighbourhood2(&origin, 1, 8, 2, -1), 4 * 5 + 35);
for position in layout.iter() {
for (direction1, direction2) in [(0, 1), (1, 2), (2, 0)] {
for offset1 in -3..=3 {
for offset2 in -3..=3 {
let stepped = layout.iter_neighbourhood(&position, direction1, offset1);
assert_eq!(
layout.neighbourhood2(
&position, direction1, offset1, direction2, offset2
),
layout.neighbourhood(&stepped, direction2, offset2)
);
}
}
}
}
}
#[test]
#[should_panic(expected = "at least one dimension")]
fn empty_dimensions_panic() {
FdmLinearOpLayout::new(Vec::new());
}
#[test]
#[should_panic(expected = "coordinate count mismatch")]
fn short_coordinates_panic() {
layout().index(&[1, 2]);
}
}