From 29b4a9ca07441b53f98ed989905dab9c17f2ee39 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Sun, 22 May 2022 20:57:30 +0200 Subject: [PATCH 01/45] WIP --- .gitignore | 2 + Cargo.toml | 9 + src/chain.rs | 967 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + src/simplex.rs | 299 +++++++++++++++ src/types.rs | 3 + 6 files changed, 1283 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/chain.rs create mode 100644 src/lib.rs create mode 100644 src/simplex.rs create mode 100644 src/types.rs diff --git a/.gitignore b/.gitignore index cc88697bc..ecef57f70 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ __pycache__/ /dist/ /nutils.egg-info/ /.eggs/ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..ad6e5f58c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "nutils-test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +approx = "0.5" diff --git a/src/chain.rs b/src/chain.rs new file mode 100644 index 000000000..753cd6bd3 --- /dev/null +++ b/src/chain.rs @@ -0,0 +1,967 @@ +use crate::simplex::Simplex; +use crate::types::{Dim, Index}; +use std::cmp::Ordering; +use std::iter; +use std::ops::Mul; + +#[derive(Debug, Clone, Copy, PartialEq)] +enum Shape { + Point, + Simplex(Simplex), +} + +trait SequenceTransformation: Clone { + /// Returns the difference between the dimension of the input and the output sequence. + fn delta_dim(&self) -> Dim; + /// Returns the length of the output sequence given the length of the input sequence. + fn len(&self, parent_len: Index) -> Index; + /// Increment the offset of the coordinate transformation, if applicable. + fn increment_offset(&mut self, increment: Dim); + /// Map the index and coordinate of an element in the output sequence to + /// the input sequence. The index is returned, the coordinate is adjusted + /// in-place. If the coordinate dimension of the input sequence is larger + /// than that of the output sequence, the [Operator::delta_dim()] last + /// elements of the coordinate are discarded. + fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index; + /// Map the index and multiple coordinates of an element in the output + /// sequence to the input sequence. The index is returned, the coordinates + /// are adjusted in-place. + fn apply_many_inplace(&self, index: Index, coordinates: &mut [f64], dim: Dim) -> Index { + let dim = dim as usize; + let mut result_index = 0; + for i in 0..coordinates.len() / dim { + result_index = self.apply_inplace(index, &mut coordinates[i * dim..(i + 1) * dim]); + } + result_index + } + /// Map the index and coordinate of an element in the output sequence to + /// the input sequence. + fn apply(&self, index: Index, coordinate: &[f64]) -> (Index, Vec) { + let delta_dim = self.delta_dim() as usize; + let to_dim = coordinate.len() + delta_dim; + let mut result = Vec::with_capacity(to_dim); + result.extend_from_slice(coordinate); + result.extend(iter::repeat(0.0).take(delta_dim)); + (self.apply_inplace(index, &mut result), result) + } + /// Map the index and multiple coordinates of an element in the output + /// sequence to the input sequence. + fn apply_many(&self, index: Index, coordinates: &[f64], dim: Dim) -> (Index, Vec) { + assert_eq!(coordinates.len() % dim as usize, 0); + let ncoords = coordinates.len() / dim as usize; + let delta_dim = self.delta_dim(); + let to_dim = dim + delta_dim; + let mut result = Vec::with_capacity(ncoords * to_dim as usize); + let mut result_index = 0; + for coord in coordinates.chunks(dim as usize) { + let offset = result.len(); + result.extend_from_slice(&coord); + result.extend(iter::repeat(0.0).take(delta_dim as usize)); + } + (self.apply_many_inplace(index, &mut result, to_dim), result) + } + //fn shape(&self, shape: Shape, offset: Dim) -> Option; +} + +#[derive(Debug, Clone, PartialEq)] +struct DimSlice { + offset: Dim, + len: Dim, +} + +impl DimSlice { + fn new(offset: Dim, len: Dim) -> Self { + Self { offset, len } + } + fn overlaps(&self, other: &DimSlice) -> bool { + match self.partial_cmp(other) { + Some(Ordering::Equal) | None => true, + Some(Ordering::Less) | Some(Ordering::Greater) => false, + } + } +} + +impl PartialOrd for DimSlice { + fn partial_cmp(&self, other: &Self) -> Option { + if self.offset + self.len <= other.offset { + Some(Ordering::Less) + } else if other.offset + other.len <= self.offset { + Some(Ordering::Greater) + } else if self == other { + Some(Ordering::Equal) + } else { + None + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Transpose { + len1: Index, + len2: Index, +} + +impl Transpose { + #[inline] + pub fn new(len1: Index, len2: Index) -> Self { + Self { len1, len2 } + } +} + +impl SequenceTransformation for Transpose { + #[inline] + fn delta_dim(&self) -> Dim { + 0 + } + #[inline] + fn len(&self, parent_len: Index) -> Index { + parent_len + } + #[inline] + fn increment_offset(&mut self, _increment: Dim) {} + #[inline] + fn apply_inplace(&self, index: Index, _coordinate: &mut [f64]) -> Index { + let low2 = index % self.len2; + let low1 = (index / self.len2) % self.len1; + let high = index / (self.len1 * self.len2); + high * (self.len1 * self.len2) + low2 * self.len1 + low1 + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Take { + indices: Box<[Index]>, + len: Index, +} + +impl Take { + #[inline] + pub fn new(indices: impl Into>, len: Index) -> Self { + Self { + indices: indices.into(), + len, + } + } +} + +impl SequenceTransformation for Take { + #[inline] + fn delta_dim(&self) -> Dim { + 0 + } + #[inline] + fn len(&self, parent_len: Index) -> Index { + (parent_len / self.len) * self.indices.len() as Index + } + #[inline] + fn increment_offset(&mut self, _increment: Dim) {} + #[inline] + fn apply_inplace(&self, index: Index, _coordinate: &mut [f64]) -> Index { + let nindices = self.indices.len() as Index; + let low = index % nindices; + let high = index / nindices; + high * self.len + self.indices[low as usize] + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Children { + simplex: Simplex, + offset: Dim, +} + +impl Children { + #[inline] + pub fn new(simplex: Simplex, offset: Dim) -> Self { + Self { simplex, offset } + } +} + +impl SequenceTransformation for Children { + #[inline] + fn delta_dim(&self) -> Dim { + 0 + } + #[inline] + fn len(&self, parent_len: Index) -> Index { + parent_len * self.simplex.nchildren() + } + #[inline] + fn increment_offset(&mut self, increment: Dim) { + self.offset += increment; + } + #[inline] + fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + self.simplex + .apply_child_inplace(index, &mut coordinate[self.offset as usize..]) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Edges { + simplex: Simplex, + offset: Dim, +} + +impl Edges { + #[inline] + pub fn new(simplex: Simplex, offset: Dim) -> Self { + Self { simplex, offset } + } +} + +impl SequenceTransformation for Edges { + #[inline] + fn delta_dim(&self) -> Dim { + 1 + } + #[inline] + fn len(&self, parent_len: Index) -> Index { + parent_len * self.simplex.nedges() + } + #[inline] + fn increment_offset(&mut self, increment: Dim) { + self.offset += increment; + } + #[inline] + fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + self.simplex + .apply_edge_inplace(index, &mut coordinate[self.offset as usize..]) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct UniformPoints { + points: Box<[f64]>, + point_dim: Dim, + offset: Dim, +} + +impl UniformPoints { + pub fn new(points: Box<[f64]>, point_dim: Dim, offset: Dim) -> Self { + Self { + points, + point_dim, + offset, + } + } +} + +impl SequenceTransformation for UniformPoints { + #[inline] + fn delta_dim(&self) -> Dim { + self.point_dim + } + #[inline] + fn len(&self, parent_len: Index) -> Index { + parent_len * (self.points.len() as Index / self.point_dim as Index) + } + #[inline] + fn increment_offset(&mut self, increment: Dim) { + self.offset += increment; + } + fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + let point_dim = self.point_dim as usize; + let coordinate = &mut coordinate[self.offset as usize..]; + coordinate.copy_within(..coordinate.len() - point_dim, point_dim); + let npoints = (self.points.len() / point_dim) as Index; + let ipoint = index % npoints; + let offset = ipoint as usize * point_dim; + coordinate[..point_dim].copy_from_slice(&self.points[offset..offset + point_dim]); + index / npoints + } +} + +/// An operator that maps a sequence of elements to another sequence of elements. +/// +/// Given a sequence of elements an [`Operator`] defines a new sequence. For +/// example [`Operator::Children`] gives the sequence of child elements and +/// [`Operator::Take`] gives a subset of the input sequence. +/// +/// All variants of [`Operator`] apply some operation to either every element of +/// the parent sequence, variants [`Operator::Children`], [`Operator::Edges`] +/// and [`Operator::UniformPoints`], or to consecutive chunks of the input +/// sequence, in which case the size of the chunks is included in the variant +/// and the input sequence is assumed to be a multiple of the chunk size long. +#[derive(Debug, Clone, PartialEq)] +pub enum Operator { + /// The transpose of a sequence: the input sequence is reshaped to `(_, + /// len1, len2)`, the last two axes are swapped and the result is + /// flattened. + Transpose(Transpose), + /// A subset of a sequence: the input sequence is reshaped to `(_, len)`, + /// the given `indices` are taken from the last axis and the result is + /// flattened. + Take(Take), + /// The children of a every element of a sequence. + Children(Children), + /// The edges of a every element of a sequence. + Edges(Edges), + UniformPoints(UniformPoints), +} + +macro_rules! impl_from_for_operator { + ($($Variant:ident),*) => {$( + impl From<$Variant> for Operator { + fn from(variant: $Variant) -> Self { + Self::$Variant(variant) + } + } + )*} +} + +macro_rules! dispatch { + ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { + match $self { + Operator::Transpose(var) => var.$fn($($arg),*), + Operator::Take(var) => var.$fn($($arg),*), + Operator::Children(var) => var.$fn($($arg),*), + Operator::Edges(var) => var.$fn($($arg),*), + Operator::UniformPoints(var) => var.$fn($($arg),*), + } + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { + match $self { + Operator::Transpose(var) => var.$fn($($arg),*), + Operator::Take(var) => var.$fn($($arg),*), + Operator::Children(var) => var.$fn($($arg),*), + Operator::Edges(var) => var.$fn($($arg),*), + Operator::UniformPoints(var) => var.$fn($($arg),*), + } + } + }; +} + +impl_from_for_operator! {Transpose, Take, Children, Edges, UniformPoints} + +impl Operator { + /// Construct a new operator that transposes a sequence of elements. + pub fn new_transpose(len1: Index, len2: Index) -> Self { + Transpose::new(len1, len2).into() + } + /// Construct a new operator that takes a subset of a sequence of elements. + pub fn new_take(indices: impl Into>, len: Index) -> Self { + Take::new(indices, len).into() + } + /// Construct a new operator that maps a sequence of elements to its children. + pub fn new_children(simplex: Simplex, offset: Dim) -> Self { + Children::new(simplex, offset).into() + } + /// Construct a new operator that maps a sequence of elements to its edges. + pub fn new_edges(simplex: Simplex, offset: Dim) -> Self { + Edges::new(simplex, offset).into() + } + /// Construct a new operator that adds points to every element of a sequence. + pub fn new_uniform_points(points: Box<[f64]>, point_dim: Dim, offset: Dim) -> Self { + UniformPoints::new(points, point_dim, offset).into() + } + pub fn swap(&self, other: &Self) -> Option> { + match (self, other) { + (_, Self::Children(children)) => { + if let Some((ops, children)) = self.swap_with_children(*children) { + Some(iter::once(children.into()).chain(ops.into_iter()).collect()) + } else { + None + } + } + (_, Self::Edges(edges)) => { + if let Some((ops, edges)) = self.swap_with_edges(*edges) { + Some(iter::once(edges.into()).chain(ops.into_iter()).collect()) + } else { + None + } + } + (Self::Transpose(a), Self::Transpose(b)) if a.len1 == b.len2 && a.len2 == b.len1 => { + Some(vec![]) + } + _ => None, + } + } + fn swap_reduce_repeat( + reduce: Operator, + reduce_before: Index, + reduce_after: Index, + nrepeat: Index, + ) -> Vec { + vec![ + Self::new_transpose(nrepeat, reduce_before), + reduce, + Self::new_transpose(reduce_after, nrepeat), + ] + } + pub fn swap_with_children(&self, other: Children) -> Option<(Vec, Children)> { + let other_slice = DimSlice::new(other.offset, other.simplex.dim()); + match self { + Self::Take(Take { indices, len }) => Some(( + Self::swap_reduce_repeat( + self.clone(), + *len, + indices.len() as Index, + other.simplex.nchildren(), + ), + other, + )), + Self::Transpose(Transpose { len1, len2 }) => Some(( + Self::swap_reduce_repeat( + self.clone(), + len1 * len2, + len1 * len2, + other.simplex.nchildren(), + ), + other, + )), + Self::Children(slf) + if !other_slice.overlaps(&DimSlice::new(slf.offset, slf.simplex.dim())) => + { + let trans = Self::new_transpose(slf.simplex.nchildren(), other.simplex.nchildren()); + Some((vec![self.clone(), trans], other)) + } + Self::Edges(slf) => { + let trans = Self::new_transpose(slf.simplex.nedges(), other.simplex.nchildren()); + match DimSlice::new(slf.offset, slf.simplex.edge_dim()).partial_cmp(&other_slice) { + Some(Ordering::Less) => { + let mut other = other; + other.offset += 1; + Some((vec![self.clone(), trans], other)) + } + Some(Ordering::Greater) => Some((vec![self.clone(), trans], other)), + Some(Ordering::Equal) => { + let indices = slf.simplex.swap_edges_children_map(); + let take = + Self::new_take(indices, slf.simplex.nchildren() * slf.simplex.nedges()); + Some(( + vec![self.clone(), take], + Children::new(slf.simplex, other.offset), + )) + } + None => None, + } + } + _ => None, + } + } + pub fn swap_with_edges(&self, other: Edges) -> Option<(Vec, Edges)> { + let other_slice = DimSlice::new(other.offset, other.simplex.dim()); + match self { + Self::Take(Take { indices, len }) => Some(( + Self::swap_reduce_repeat( + self.clone(), + *len, + indices.len() as Index, + other.simplex.nedges(), + ), + other, + )), + Self::Transpose(Transpose { len1, len2 }) => Some(( + Self::swap_reduce_repeat( + self.clone(), + len1 * len2, + len1 * len2, + other.simplex.nedges(), + ), + other, + )), + Self::Children(slf) => { + let trans = Self::new_transpose(slf.simplex.nchildren(), other.simplex.nedges()); + match DimSlice::new(slf.offset, slf.simplex.dim()).partial_cmp(&other_slice) { + Some(Ordering::Less) => Some((vec![self.clone(), trans], other)), + Some(Ordering::Greater) => { + let mut slf = slf.clone(); + slf.offset -= 1; + Some((vec![slf.into(), trans], other)) + } + Some(Ordering::Equal) | None => None, + } + } + Self::Edges(slf) => { + let trans = Self::new_transpose(slf.simplex.nedges(), other.simplex.nedges()); + match DimSlice::new(slf.offset, slf.simplex.edge_dim()).partial_cmp(&other_slice) { + Some(Ordering::Less) => { + let mut other = other; + other.offset += 1; + Some((vec![self.clone(), trans], other)) + } + Some(Ordering::Greater) => { + let mut slf = slf.clone(); + slf.offset -= 1; + Some((vec![slf.into(), trans], other)) + } + Some(Ordering::Equal) | None => None, + } + } + _ => None, + } + } +} + +impl SequenceTransformation for Operator { + dispatch! {fn delta_dim(&self) -> Dim} + dispatch! {fn len(&self, parent_len: Index) -> Index} + dispatch! {fn increment_offset(&mut self, increment: Dim)} + dispatch! {fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index} + dispatch! {fn apply_many_inplace(&self, index: Index, coordinates: &mut [f64], dim: Dim) -> Index} + dispatch! {fn apply(&self, index: Index, coordinate: &[f64]) -> (Index, Vec)} + dispatch! {fn apply_many(&self, index: Index, coordinates: &[f64], dim: Dim) -> (Index, Vec)} +} + +/// A chain of [`Operator`]s. +#[derive(Debug, Clone)] +pub struct Chain { + rev_operators: Vec, +} + +impl Chain { + #[inline] + pub fn new(operators: Operators) -> Self + where + Operators: IntoIterator, + Operators::IntoIter: DoubleEndedIterator, + { + Chain { + rev_operators: operators.into_iter().rev().collect(), + } + } + /// Returns a clone of this [`Chain`] with the given `operator` appended. + #[inline] + pub fn clone_and_push(&self, operator: Operator) -> Self { + Self::new( + self.rev_operators + .iter() + .rev() + .cloned() + .chain(iter::once(operator)), + ) + } + #[inline] + pub fn iter_operators(&self) -> impl Iterator + DoubleEndedIterator { + self.rev_operators.iter().rev() + } + /// Remove and return the common prefix of two chains, transforming either if necessary. + pub fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { + let mut common = Vec::new(); + let mut rev_a = self.rev_operators.clone(); + let mut rev_b = other.rev_operators.clone(); + while !rev_a.is_empty() && !rev_b.is_empty() { + if rev_a.last() == rev_b.last() { + common.push(rev_a.pop().unwrap()); + rev_b.pop(); + continue; + } + // [Edges(Line)], [Children(Line)] -> [Children(Line), Take, Edges(Line)], [Children(Line)] + // List all edges and children that can be moved to the front + // and remove the common operator with the least amount of + // operators in front. + break; + } + let common = if rev_a.is_empty() + && (!rev_b.is_empty() || self.rev_operators.len() <= other.rev_operators.len()) + { + self.clone() + } else if rev_b.is_empty() { + other.clone() + } else { + Self::new(common) + }; + ( + common, + Self::new(rev_a.into_iter().rev()), + Self::new(rev_b.into_iter().rev()), + ) + } +} + +impl SequenceTransformation for Chain { + #[inline] + fn delta_dim(&self) -> Dim { + self.rev_operators.iter().map(|op| op.delta_dim()).sum() + } + #[inline] + fn len(&self, parent_len: Index) -> Index { + self.rev_operators + .iter() + .rfold(parent_len, |len, op| op.len(len)) + } + #[inline] + fn increment_offset(&mut self, increment: Dim) { + for op in self.rev_operators.iter_mut() { + op.increment_offset(increment); + } + } + #[inline] + fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + self.rev_operators + .iter() + .fold(index, |index, op| op.apply_inplace(index, coordinate)) + } + #[inline] + fn apply_many_inplace(&self, index: Index, coordinates: &mut [f64], dim: Dim) -> Index { + self.rev_operators.iter().fold(index, |index, op| { + op.apply_many_inplace(index, coordinates, dim) + }) + } +} + +//#[derive(Debug, Clone)] +//pub struct ConcatChain(Vec<(Chain, Index)>); + +#[derive(Debug, Clone)] +pub struct Topology { + transforms: Chain, + dim: Dim, + root_len: Index, + len: Index, +} + +impl Topology { + pub fn new(dim: Dim, len: Index) -> Self { + Self { + transforms: Chain::new([]), + dim, + root_len: len, + len, + } + } + pub fn derive(&self, operator: Operator) -> Self { + Self { + root_len: self.root_len, + len: operator.len(self.len), + dim: self.dim - operator.delta_dim(), + transforms: self.transforms.clone_and_push(operator), + } + } +} + +impl Mul for &Topology { + type Output = Topology; + + fn mul(self, other: &Topology) -> Topology { + Topology { + transforms: Chain::new( + iter::once(Operator::new_transpose(other.root_len, self.root_len)) + .chain(self.transforms.iter_operators().cloned()) + .chain(iter::once(Operator::new_transpose( + self.len, + other.root_len, + ))) + .chain(other.transforms.iter_operators().map(|op| { + let mut op = op.clone(); + op.increment_offset(self.dim); + op + })), + ), + dim: self.dim + other.dim, + root_len: self.root_len * other.root_len, + len: self.len * other.len, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use approx::assert_abs_diff_eq; + use Simplex::*; + + macro_rules! assert_eq_op_apply { + ($op:expr, $ii:expr, $ic:expr, $oi:expr, $oc:expr) => {{ + let ic = $ic; + let oc = $oc; + let mut work = oc.clone(); + for i in 0..ic.len() { + work[i] = ic[i]; + } + for i in ic.len()..oc.len() { + work[i] = 0.0; + } + assert_eq!($op.apply_inplace($ii, &mut work), $oi); + assert_abs_diff_eq!(work[..], oc[..]); + }}; + } + + #[test] + fn apply_children_line() { + let op = Operator::new_children(Line, 0); + assert_eq_op_apply!(op, 0 * 2 + 0, [0.0], 0, [0.0]); + assert_eq_op_apply!(op, 1 * 2 + 0, [1.0], 1, [0.5]); + assert_eq_op_apply!(op, 2 * 2 + 1, [0.0], 2, [0.5]); + assert_eq_op_apply!(op, 3 * 2 + 1, [1.0], 3, [1.0]); + assert_eq_op_apply!(op, 0, [0.0, 2.0], 0, [0.0, 2.0]); + assert_eq_op_apply!(op, 1, [0.0, 3.0, 4.0], 0, [0.5, 3.0, 4.0]); + let op = Operator::new_children(Line, 1); + assert_eq_op_apply!(op, 1, [2.0, 0.0], 0, [2.0, 0.5]); + assert_eq_op_apply!(op, 1, [3.0, 0.0, 4.0], 0, [3.0, 0.5, 4.0]); + } + + #[test] + fn apply_edges_line() { + let op = Operator::new_edges(Line, 0); + assert_eq_op_apply!(op, 0, [], 0, [1.0]); + assert_eq_op_apply!(op, 3, [], 1, [0.0]); + assert_eq_op_apply!(op, 4, [], 2, [1.0]); + assert_eq_op_apply!(op, 7, [], 3, [0.0]); + assert_eq_op_apply!(op, 0, [2.0], 0, [1.0, 2.0]); + assert_eq_op_apply!(op, 1, [3.0, 4.0], 0, [0.0, 3.0, 4.0]); + let op = Operator::new_edges(Line, 1); + assert_eq_op_apply!(op, 0, [2.0], 0, [2.0, 1.0]); + assert_eq_op_apply!(op, 0, [3.0, 4.0], 0, [3.0, 1.0, 4.0]); + } + + // #[test] + // fn apply_edges_square() { + // let op = Operator::Edges { + // simplices: Box::new([Line, Line]), + // offset: 0, + // }; + // assert_eq!(op.apply(0 * 4 + 0, &[0.0]), (0, vec![1.0, 0.0])); + // assert_eq!(op.apply(1 * 4 + 0, &[1.0]), (1, vec![1.0, 1.0])); + // assert_eq!(op.apply(2 * 4 + 1, &[0.0]), (2, vec![0.0, 0.0])); + // assert_eq!(op.apply(3 * 4 + 1, &[1.0]), (3, vec![0.0, 1.0])); + // assert_eq!(op.apply(4 * 4 + 2, &[0.0]), (4, vec![0.0, 1.0])); + // assert_eq!(op.apply(5 * 4 + 2, &[1.0]), (5, vec![1.0, 1.0])); + // assert_eq!(op.apply(6 * 4 + 3, &[0.0]), (6, vec![0.0, 0.0])); + // assert_eq!(op.apply(7 * 4 + 3, &[1.0]), (7, vec![1.0, 0.0])); + // assert_eq!(op.apply(0, &[0.0, 2.0]), (0, vec![1.0, 0.0, 2.0])); + // assert_eq!(op.apply(1, &[0.0, 3.0, 4.0]), (0, vec![0.0, 0.0, 3.0, 4.0])); + // } + + #[test] + fn apply_transpose_index() { + let op = Operator::new_transpose(2, 3); + for i in 0..3 { + for j in 0..2 { + for k in 0..3 { + assert_eq!( + op.apply((i * 2 + j) * 3 + k, &[]), + ((i * 3 + k) * 2 + j, vec![]) + ); + } + } + } + } + + #[test] + fn apply_take_all() { + let op = Operator::new_take([3, 2, 0, 4, 1], 5); // inverse: [2, 4, 1, 0, 3] + assert_eq_op_apply!(op, 0, [], 3, []); + assert_eq_op_apply!(op, 6, [1.0], 7, [1.0]); + assert_eq_op_apply!(op, 12, [2.0, 3.0], 10, [2.0, 3.0]); + assert_eq_op_apply!(op, 18, [], 19, []); + assert_eq_op_apply!(op, 24, [], 21, []); + } + + #[test] + fn apply_take_some() { + let op = Operator::new_take([4, 0, 1], 5); // inverse: [1, 2, x, x, 0] + assert_eq_op_apply!(op, 0, [], 4, []); + assert_eq_op_apply!(op, 4, [1.0], 5, [1.0]); + assert_eq_op_apply!(op, 8, [2.0, 3.0], 11, [2.0, 3.0]); + } + + #[test] + fn apply_uniform_points() { + let op = Operator::new_uniform_points(Box::new([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]), 2, 0); + assert_eq_op_apply!(op, 0, [], 0, [0.0, 1.0]); + assert_eq_op_apply!(op, 4, [6.0], 1, [2.0, 3.0, 6.0]); + assert_eq_op_apply!(op, 8, [7.0, 8.0], 2, [4.0, 5.0, 7.0, 8.0]); + } + + #[test] + fn mul_topo() { + let xtopo = Topology::new(1, 2).derive(Operator::new_children(Line, 0)); + let ytopo = Topology::new(1, 3).derive(Operator::new_children(Line, 0)); + let xytopo = &xtopo * &ytopo; + assert_eq!(xtopo.len, 4); + assert_eq!(ytopo.len, 6); + assert_eq!(xytopo.len, 24); + assert_eq!(xytopo.root_len, 6); + for i in 0..4 { + for j in 0..6 { + let x = xtopo.transforms.apply_many(i, &[0.0, 0.0, 1.0, 1.0], 1).1; + let y = ytopo.transforms.apply_many(j, &[0.0, 1.0, 0.0, 1.0], 1).1; + let mut xy = Vec::with_capacity(8); + for k in 0..4 { + xy.push(x[k]); + xy.push(y[k]); + } + assert_eq!( + xytopo.transforms.apply_many( + i * 6 + j, + &[0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0], + 2 + ), + ((i / 2) * 3 + j / 2, xy), + ); + } + } + } + + macro_rules! assert_equiv_topo { + ($topo1:expr, $topo2:expr$(, $simplex:ident)*) => { + #[allow(unused_mut)] + let mut topo1 = $topo1.clone(); + #[allow(unused_mut)] + let mut topo2 = $topo2.clone(); + assert_eq!(topo1.dim, topo2.dim, "topos have different dim"); + assert_eq!(topo1.len, topo2.len, "topos have different len"); + assert_eq!(topo1.root_len, topo2.root_len, "topos have different root_len"); + let from_dim = 0$(+$simplex.dim())*; + assert_eq!(topo1.dim, from_dim, "dimension of topo differs from dimension of given simplices"); + let nelems = topo1.len; + $( + let points = Operator::new_uniform_points( + $simplex.vertices().into(), + $simplex.dim(), + 0, + ); + topo1 = topo1.derive(points.clone()); + topo2 = topo2.derive(points.clone()); + )* + let npoints = topo1.len; + let mut coord1: Vec<_> = iter::repeat(0.0).take((topo1.dim + topo1.transforms.delta_dim()) as usize).collect(); + let mut coord2 = coord1.clone(); + for i in 0..topo1.len { + let ielem = i / (npoints / nelems); + assert_eq!( + topo1.transforms.apply_inplace(i, &mut coord1), + topo2.transforms.apply_inplace(i, &mut coord2), + "topo1 and topo2 map element {ielem} to different root elements" + ); + assert_abs_diff_eq!(coord1[..], coord2[..]); + } + }; + } + + #[test] + fn swap_edges_children_1d() { + let topo1 = Topology::new(1, 3).derive(Operator::new_edges(Line, 0)); + let topo2 = Topology::new(1, 3) + .derive(Operator::new_children(Line, 0)) + .derive(Operator::new_edges(Line, 0)) + .derive(Operator::new_take([2, 1], 4)); + assert_equiv_topo!(topo1, topo2); + } + + #[test] + fn swap_take_children() { + let take = Operator::new_take([2, 3, 1], 5); + let children = Operator::new_children(Line, 0); + let swapped = vec![ + children.clone(), + Operator::new_transpose(2, 5), + take.clone(), + Operator::new_transpose(3, 2), + ]; + let base = Topology::new(1, 5); + assert_eq!(take.swap(&children), Some(swapped.clone())); + assert_equiv_topo!( + base.derive(take).derive(children), + swapped + .iter() + .cloned() + .fold(base.clone(), |t, o| t.derive(o)), + Line + ); + } + + #[test] + fn swap_take_edges() { + let take = Operator::new_take([2, 3, 1], 5); + let edges = Operator::new_edges(Line, 0); + let swapped = vec![ + edges.clone(), + Operator::new_transpose(2, 5), + take.clone(), + Operator::new_transpose(3, 2), + ]; + let base = Topology::new(1, 5); + assert_eq!(take.swap(&edges), Some(swapped.clone())); + assert_equiv_topo!( + base.derive(take).derive(edges), + swapped + .iter() + .cloned() + .fold(base.clone(), |t, o| t.derive(o)) + ); + } + + macro_rules! fn_test_operator_swap { + ($name:ident, $len:expr $(, $simplex:ident)*; $op1:expr, $op2:expr,) => { + #[test] + fn $name() { + let op1: Operator = $op1; + let op2: Operator = $op2; + let swapped = op1.swap(&op2).expect("not swapped"); + println!("op1: {op1:?}"); + println!("op2: {op2:?}"); + println!("swapped: {swapped:?}"); + let root_dim = op1.delta_dim() + op2.delta_dim() $(+ $simplex.dim())*; + let base = Topology::new(root_dim, 1); + let topo1 = [op1, op2].iter().fold(base.clone(), |t, o| t.derive(o.clone())); + let topo2 = swapped.iter().fold(base, |t, o| t.derive(o.clone())); + let len = $len; + assert_eq!(topo1.len, len, "unswapped topo has unexpected length"); + assert_eq!(topo2.len, len, "swapped topo has unexpected length"); + assert_equiv_topo!(topo1, topo2 $(, $simplex)*); + } + } + } + + fn_test_operator_swap! { + swap_edges_children_triangle1, 6, Line, Line; + Operator::new_edges(Triangle, 0), + Operator::new_children(Line, 0), + } + + fn_test_operator_swap! { + swap_unoverlapping_children_lt_children, 8, Triangle, Line; + Operator::new_children(Triangle, 0), + Operator::new_children(Line, 2), + } + + fn_test_operator_swap! { + swap_unoverlapping_children_gt_children, 8, Line, Triangle; + Operator::new_children(Line, 2), + Operator::new_children(Triangle, 0), + } + + fn_test_operator_swap! { + swap_unoverlapping_edges_lt_children, 6, Line, Line; + Operator::new_edges(Triangle, 0), + Operator::new_children(Line, 1), + } + + fn_test_operator_swap! { + swap_unoverlapping_edges_gt_children, 6, Line, Line; + Operator::new_edges(Triangle, 1), + Operator::new_children(Line, 0), + } + + fn_test_operator_swap! { + swap_unoverlapping_children_lt_edges, 6, Line, Line; + Operator::new_children(Line, 0), + Operator::new_edges(Triangle, 1), + } + + fn_test_operator_swap! { + swap_unoverlapping_children_gt_edges, 6, Line, Line; + Operator::new_children(Line, 2), + Operator::new_edges(Triangle, 0), + } + + fn_test_operator_swap! { + swap_unoverlapping_edges_lt_edges, 6, Line; + Operator::new_edges(Line, 0), + Operator::new_edges(Triangle, 0), + } + + fn_test_operator_swap! { + swap_unoverlapping_edges_gt_edges, 6, Line; + Operator::new_edges(Line, 2), + Operator::new_edges(Triangle, 0), + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..869ca3ce3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod simplex; +pub mod types; +pub mod chain; diff --git a/src/simplex.rs b/src/simplex.rs new file mode 100644 index 000000000..a828a0ffa --- /dev/null +++ b/src/simplex.rs @@ -0,0 +1,299 @@ +use crate::types::{Dim, Index}; + +/// Simplex. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Simplex { + Line, + Triangle, +} + +impl Simplex { + /// Returns the dimension of the simplex. + #[inline] + pub const fn dim(&self) -> Dim { + match self { + Self::Line => 1, + Self::Triangle => 2, + } + } + /// Returns the dimension of an edge of the simplex. + #[inline] + pub const fn edge_dim(&self) -> Dim { + self.dim() - 1 + } + /// Returns the number of edges of the simplex. + #[inline] + pub const fn nedges(&self) -> Index { + match self { + Self::Line => 2, + Self::Triangle => 3, + } + } + /// Returns the number of children of the simplex. + #[inline] + pub const fn nchildren(&self) -> Index { + match self { + Self::Line => 2, + Self::Triangle => 4, + } + } + + const LINE_SWAP_EDGES_CHILDREN_MAP: [Index; 2] = [2, 1]; + const TRIANGLE_SWAP_EDGES_CHILDREN_MAP: [Index; 6] = [3, 6, 1, 7, 2, 5]; + + /// Returns an array of indices of edges of children corresponding to children of edges. + #[inline] + pub const fn swap_edges_children_map(&self) -> &'static [Index] { + match self { + Self::Line => &Self::LINE_SWAP_EDGES_CHILDREN_MAP, + Self::Triangle => &Self::TRIANGLE_SWAP_EDGES_CHILDREN_MAP, + } + } + + const LINE_CONNECTIVITY: [Option; 4] = [Some(3), None, None, Some(0)]; + const TRIANGLE_CONNECTIVITY: [Option; 12] = [ + Some(11), + None, + None, + None, + Some(10), + None, + None, + None, + Some(9), + Some(8), + Some(4), + Some(0), + ]; + + /// Returns an array of indices of opposite edges of children, or `None` if + /// an edge lies on the boundary of the simplex. + #[inline] + pub const fn connectivity(&self) -> &'static [Option] { + match self { + Self::Line => &Self::LINE_CONNECTIVITY, + Self::Triangle => &Self::TRIANGLE_CONNECTIVITY, + } + } + + const LINE_VERTICES: [f64; 2] = [0.0, 1.0]; + const TRIANGLE_VERTICES: [f64; 6] = [0.0, 0.0, 0.0, 1.0, 1.0, 0.0]; + + /// Returns an array of vertices. + #[inline] + pub const fn vertices(&self) -> &'static [f64] { + match self { + Self::Line => &Self::LINE_VERTICES, + Self::Triangle => &Self::TRIANGLE_VERTICES, + } + } + + /// Transform the given child `coordinate` for child `index` to this parent + /// simplex in-place. The returned index is the index of the parent in an + /// infinite, uniform sequence. + pub fn apply_child_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + match self { + Self::Line => { + coordinate[0] = 0.5 * (coordinate[0] + (index % 2) as f64); + index / 2 + } + Self::Triangle => { + coordinate[0] *= 0.5; + coordinate[1] *= 0.5; + match index % 4 { + 1 => { + coordinate[0] += 0.5; + } + 2 => { + coordinate[1] += 0.5; + } + 3 => { + coordinate[1] += coordinate[0]; + coordinate[0] = 0.5 - coordinate[0]; + } + _ => {} + } + index / 4 + } + } + } + /// Transform the given edge `coordinate` for edge `index` to this parent + /// simplex in-place. The returned index is the index of the parent in an + /// infinite, uniform sequence. + pub fn apply_edge_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + coordinate.copy_within( + self.edge_dim() as usize..coordinate.len() - 1, + self.dim() as usize, + ); + match self { + Self::Line => { + coordinate[0] = (1 - index % 2) as f64; + index / 2 + } + Self::Triangle => { + match index % 3 { + 0 => { + coordinate[1] = coordinate[0]; + coordinate[0] = 1.0 - coordinate[0]; + } + 1 => { + coordinate[1] = coordinate[0]; + coordinate[0] = 0.0; + } + _ => { + coordinate[1] = 0.0; + } + } + index / 3 + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use approx::assert_abs_diff_eq; + use Simplex::*; + + macro_rules! assert_child_index_coord { + ($simplex:ident, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ + let incoords = $incoords; + let outcoords = $outcoords; + for i in 0..outcoords.len() { + let mut work = outcoords[i].clone(); + for j in 0..incoords[i].len() { + work[j] = incoords[i][j]; + } + for j in incoords[i].len()..outcoords[i].len() { + work[j] = -1.0; + } + assert_eq!($simplex.apply_child_inplace($inidx, &mut work), $outidx); + assert_abs_diff_eq!(work[..], outcoords[i][..]); + } + }}; + } + + macro_rules! assert_edge_index_coord { + ($simplex:ident, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ + let incoords = $incoords; + let outcoords = $outcoords; + for i in 0..outcoords.len() { + let mut work = outcoords[i].clone(); + for j in 0..incoords[i].len() { + work[j] = incoords[i][j]; + } + for j in incoords[i].len()..outcoords[i].len() { + work[j] = -1.0; + } + assert_eq!($simplex.apply_edge_inplace($inidx, &mut work), $outidx); + assert_abs_diff_eq!(work[..], outcoords[i][..]); + } + }}; + } + + #[test] + fn line_child_coords() { + assert_child_index_coord!(Line, 2, [[0.0], [0.5], [1.0]], 1, [[0.0], [0.25], [0.5]]); + assert_child_index_coord!(Line, 5, [[0.0], [0.5], [1.0]], 2, [[0.5], [0.75], [1.0]]); + } + + #[test] + fn line_edge_coords() { + assert_edge_index_coord!(Line, 2, [[]], 1, [[1.0]]); + assert_edge_index_coord!(Line, 5, [[]], 2, [[0.0]]); + } + + #[test] + fn triangle_child_coords() { + assert_child_index_coord!( + Triangle, + 4, + [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]], + 1, + [[0.0, 0.0], [0.5, 0.0], [0.0, 0.5]] + ); + assert_child_index_coord!( + Triangle, + 9, + [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]], + 2, + [[0.5, 0.0], [1.0, 0.0], [0.5, 0.5]] + ); + assert_child_index_coord!( + Triangle, + 14, + [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]], + 3, + [[0.0, 0.5], [0.5, 0.5], [0.0, 1.0]] + ); + assert_child_index_coord!( + Triangle, + 19, + [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]], + 4, + [[0.5, 0.0], [0.0, 0.5], [0.5, 0.5]] + ); + } + + #[test] + fn triangle_edge_coords() { + assert_edge_index_coord!(Triangle, 3, [[0.0], [1.0]], 1, [[1.0, 0.0], [0.0, 1.0]]); + assert_edge_index_coord!(Triangle, 7, [[0.0], [1.0]], 2, [[0.0, 0.0], [0.0, 1.0]]); + assert_edge_index_coord!(Triangle, 11, [[0.0], [1.0]], 3, [[0.0, 0.0], [1.0, 0.0]]); + } + + #[test] + fn line_swap_edges_children_map() { + for (i, j) in Line.swap_edges_children_map().iter().cloned().enumerate() { + let mut x = [0.5]; + let mut y = [0.5]; + Line.apply_edge_inplace(i as Index, &mut x); + Line.apply_child_inplace(Line.apply_edge_inplace(j, &mut y), &mut y); + assert_abs_diff_eq!(x[..], y[..]); + } + } + + #[test] + fn triangle_swap_edges_children_map() { + for (i, j) in Triangle + .swap_edges_children_map() + .iter() + .cloned() + .enumerate() + { + let mut x = [0.5, 0.5]; + let mut y = [0.5, 0.5]; + Triangle.apply_edge_inplace(Line.apply_child_inplace(i as Index, &mut x), &mut x); + Triangle.apply_child_inplace(Triangle.apply_edge_inplace(j, &mut y), &mut y); + assert_abs_diff_eq!(x[..], y[..]); + } + } + + #[test] + fn line_connectivity() { + for (i, j) in Line.connectivity().iter().cloned().enumerate() { + if let Some(j) = j { + let mut x = [0.5]; + let mut y = [0.5]; + Line.apply_child_inplace(Line.apply_edge_inplace(i as Index, &mut x), &mut x); + Line.apply_child_inplace(Line.apply_edge_inplace(j, &mut y), &mut y); + assert_abs_diff_eq!(x[..], y[..]); + } + } + } + + #[test] + fn triangle_connectivity() { + for (i, j) in Triangle.connectivity().iter().cloned().enumerate() { + if let Some(j) = j { + let i = i as Index; + let mut x = [0.5, 0.5]; + let mut y = [0.5, 0.5]; + Triangle.apply_child_inplace(Triangle.apply_edge_inplace(i, &mut x), &mut x); + Triangle.apply_child_inplace(Triangle.apply_edge_inplace(j, &mut y), &mut y); + assert_abs_diff_eq!(x[..], y[..]); + } + } + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 000000000..a82167ffc --- /dev/null +++ b/src/types.rs @@ -0,0 +1,3 @@ +pub type Dim = u8; +pub type Index = u64; + From 23dfaa8dc23cdad9b1fa55519e94ac280ec7a5e4 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 1 Jun 2022 22:51:22 +0200 Subject: [PATCH 02/45] WIP --- src/chain.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 753cd6bd3..f6ea9d6d0 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -95,6 +95,27 @@ impl PartialOrd for DimSlice { } } +trait InputDimSlice { + fn input_dim_slice(&self) -> DimSlice; +} + +trait OutputDimSlice { + fn output_dim_slice(&self) -> DimSlice; +} + +fn compare_dimensions(left: &impl InputDimSlice, right: &impl OutputDimSlice) -> Option { + let left = left.input_dim_slice(); + let right = right.output_dim_slice(); + left.partial_cmp(&right) +} + +fn dimensions_overlap(left: &impl InputDimSlice, right: &impl OutputDimSlice) -> bool { + match compare_dimensions(left, right) { + Some(Ordering::Equal) | None => true, + Some(Ordering::Less) | Some(Ordering::Greater) => false, + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub struct Transpose { len1: Index, @@ -197,6 +218,20 @@ impl SequenceTransformation for Children { } } +impl InputDimSlice for Children { + #[inline] + fn input_dim_slice(&self) -> DimSlice { + DimSlice::new(self.offset, self.simplex.dim()) + } +} + +impl OutputDimSlice for Children { + #[inline] + fn output_dim_slice(&self) -> DimSlice { + DimSlice::new(self.offset, self.simplex.dim()) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub struct Edges { simplex: Simplex, @@ -230,6 +265,20 @@ impl SequenceTransformation for Edges { } } +impl InputDimSlice for Edges { + #[inline] + fn input_dim_slice(&self) -> DimSlice { + DimSlice::new(self.offset, self.simplex.edge_dim()) + } +} + +impl OutputDimSlice for Edges { + #[inline] + fn output_dim_slice(&self) -> DimSlice { + DimSlice::new(self.offset, self.simplex.dim()) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct UniformPoints { points: Box<[f64]>, @@ -395,7 +444,6 @@ impl Operator { ] } pub fn swap_with_children(&self, other: Children) -> Option<(Vec, Children)> { - let other_slice = DimSlice::new(other.offset, other.simplex.dim()); match self { Self::Take(Take { indices, len }) => Some(( Self::swap_reduce_repeat( @@ -415,15 +463,13 @@ impl Operator { ), other, )), - Self::Children(slf) - if !other_slice.overlaps(&DimSlice::new(slf.offset, slf.simplex.dim())) => - { + Self::Children(slf) if !dimensions_overlap(slf, &other) => { let trans = Self::new_transpose(slf.simplex.nchildren(), other.simplex.nchildren()); Some((vec![self.clone(), trans], other)) } Self::Edges(slf) => { let trans = Self::new_transpose(slf.simplex.nedges(), other.simplex.nchildren()); - match DimSlice::new(slf.offset, slf.simplex.edge_dim()).partial_cmp(&other_slice) { + match compare_dimensions(slf, &other) { Some(Ordering::Less) => { let mut other = other; other.offset += 1; @@ -446,7 +492,6 @@ impl Operator { } } pub fn swap_with_edges(&self, other: Edges) -> Option<(Vec, Edges)> { - let other_slice = DimSlice::new(other.offset, other.simplex.dim()); match self { Self::Take(Take { indices, len }) => Some(( Self::swap_reduce_repeat( @@ -468,7 +513,7 @@ impl Operator { )), Self::Children(slf) => { let trans = Self::new_transpose(slf.simplex.nchildren(), other.simplex.nedges()); - match DimSlice::new(slf.offset, slf.simplex.dim()).partial_cmp(&other_slice) { + match compare_dimensions(slf, &other) { Some(Ordering::Less) => Some((vec![self.clone(), trans], other)), Some(Ordering::Greater) => { let mut slf = slf.clone(); @@ -480,7 +525,7 @@ impl Operator { } Self::Edges(slf) => { let trans = Self::new_transpose(slf.simplex.nedges(), other.simplex.nedges()); - match DimSlice::new(slf.offset, slf.simplex.edge_dim()).partial_cmp(&other_slice) { + match compare_dimensions(slf, &other) { Some(Ordering::Less) => { let mut other = other; other.offset += 1; From afe7f4c6990e08f5898399dac93631b3f9b8f015 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 1 Jun 2022 23:30:26 +0200 Subject: [PATCH 03/45] WIP --- src/chain.rs | 242 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 149 insertions(+), 93 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index f6ea9d6d0..b8b90fd58 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -444,102 +444,19 @@ impl Operator { ] } pub fn swap_with_children(&self, other: Children) -> Option<(Vec, Children)> { - match self { - Self::Take(Take { indices, len }) => Some(( - Self::swap_reduce_repeat( - self.clone(), - *len, - indices.len() as Index, - other.simplex.nchildren(), - ), - other, - )), - Self::Transpose(Transpose { len1, len2 }) => Some(( - Self::swap_reduce_repeat( - self.clone(), - len1 * len2, - len1 * len2, - other.simplex.nchildren(), - ), - other, - )), - Self::Children(slf) if !dimensions_overlap(slf, &other) => { - let trans = Self::new_transpose(slf.simplex.nchildren(), other.simplex.nchildren()); - Some((vec![self.clone(), trans], other)) - } - Self::Edges(slf) => { - let trans = Self::new_transpose(slf.simplex.nedges(), other.simplex.nchildren()); - match compare_dimensions(slf, &other) { - Some(Ordering::Less) => { - let mut other = other; - other.offset += 1; - Some((vec![self.clone(), trans], other)) - } - Some(Ordering::Greater) => Some((vec![self.clone(), trans], other)), - Some(Ordering::Equal) => { - let indices = slf.simplex.swap_edges_children_map(); - let take = - Self::new_take(indices, slf.simplex.nchildren() * slf.simplex.nedges()); - Some(( - vec![self.clone(), take], - Children::new(slf.simplex, other.offset), - )) - } - None => None, - } - } - _ => None, + let mut other = other; + if let Some(tail) = other.swap(self) { + Some((tail, other)) + } else { + None } } pub fn swap_with_edges(&self, other: Edges) -> Option<(Vec, Edges)> { - match self { - Self::Take(Take { indices, len }) => Some(( - Self::swap_reduce_repeat( - self.clone(), - *len, - indices.len() as Index, - other.simplex.nedges(), - ), - other, - )), - Self::Transpose(Transpose { len1, len2 }) => Some(( - Self::swap_reduce_repeat( - self.clone(), - len1 * len2, - len1 * len2, - other.simplex.nedges(), - ), - other, - )), - Self::Children(slf) => { - let trans = Self::new_transpose(slf.simplex.nchildren(), other.simplex.nedges()); - match compare_dimensions(slf, &other) { - Some(Ordering::Less) => Some((vec![self.clone(), trans], other)), - Some(Ordering::Greater) => { - let mut slf = slf.clone(); - slf.offset -= 1; - Some((vec![slf.into(), trans], other)) - } - Some(Ordering::Equal) | None => None, - } - } - Self::Edges(slf) => { - let trans = Self::new_transpose(slf.simplex.nedges(), other.simplex.nedges()); - match compare_dimensions(slf, &other) { - Some(Ordering::Less) => { - let mut other = other; - other.offset += 1; - Some((vec![self.clone(), trans], other)) - } - Some(Ordering::Greater) => { - let mut slf = slf.clone(); - slf.offset -= 1; - Some((vec![slf.into(), trans], other)) - } - Some(Ordering::Equal) | None => None, - } - } - _ => None, + let mut other = other; + if let Some(tail) = other.swap(self) { + Some((tail, other)) + } else { + None } } } @@ -554,6 +471,145 @@ impl SequenceTransformation for Operator { dispatch! {fn apply_many(&self, index: Index, coordinates: &[f64], dim: Dim) -> (Index, Vec)} } +trait Swap { + fn swap(&mut self, other: &L) -> Option>; +} + +impl Swap for Children { + fn swap(&mut self, other: &Children) -> Option> { + match compare_dimensions(other, self) { + Some(Ordering::Equal) | None => None, + Some(Ordering::Less) | Some(Ordering::Greater) => { + let trans = + Operator::new_transpose(other.simplex.nchildren(), self.simplex.nchildren()); + Some(vec![other.clone().into(), trans]) + } + } + } +} + +impl Swap for Children { + fn swap(&mut self, other: &Edges) -> Option> { + let trans = Operator::new_transpose(other.simplex.nedges(), self.simplex.nchildren()); + match compare_dimensions(other, self) { + Some(Ordering::Less) => { + self.offset += 1; + Some(vec![other.clone().into(), trans]) + } + Some(Ordering::Greater) => Some(vec![other.clone().into(), trans]), + Some(Ordering::Equal) => { + let indices = other.simplex.swap_edges_children_map(); + let take = + Operator::new_take(indices, other.simplex.nchildren() * other.simplex.nedges()); + self.simplex = other.simplex; + Some(vec![other.clone().into(), take]) + } + None => None, + } + } +} + +impl Swap for Edges { + fn swap(&mut self, other: &Children) -> Option> { + let trans = Operator::new_transpose(other.simplex.nchildren(), self.simplex.nedges()); + match compare_dimensions(other, self) { + Some(Ordering::Less) => Some(vec![other.clone().into(), trans]), + Some(Ordering::Greater) => { + let mut other = other.clone(); + other.offset -= 1; + Some(vec![other.into(), trans]) + } + Some(Ordering::Equal) | None => None, + } + } +} + +impl Swap for Edges { + fn swap(&mut self, other: &Edges) -> Option> { + let trans = Operator::new_transpose(other.simplex.nedges(), self.simplex.nedges()); + match compare_dimensions(other, self) { + Some(Ordering::Less) => { + self.offset += 1; + Some(vec![other.clone().into(), trans]) + } + Some(Ordering::Greater) => { + let mut other = other.clone(); + other.offset -= 1; + Some(vec![other.into(), trans]) + } + Some(Ordering::Equal) | None => None, + } + } +} + +impl Swap for Children { + fn swap(&mut self, other: &Take) -> Option> { + Some(Operator::swap_reduce_repeat( + other.clone().into(), + other.len, + other.indices.len() as Index, + self.simplex.nchildren(), + )) + } +} + +impl Swap for Edges { + fn swap(&mut self, other: &Take) -> Option> { + Some(Operator::swap_reduce_repeat( + other.clone().into(), + other.len, + other.indices.len() as Index, + self.simplex.nedges(), + )) + } +} + +impl Swap for Children { + fn swap(&mut self, other: &Transpose) -> Option> { + Some(Operator::swap_reduce_repeat( + other.clone().into(), + other.len1 * other.len2, + other.len1 * other.len2, + self.simplex.nchildren(), + )) + } +} + +impl Swap for Edges { + fn swap(&mut self, other: &Transpose) -> Option> { + Some(Operator::swap_reduce_repeat( + other.clone().into(), + other.len1 * other.len2, + other.len1 * other.len2, + self.simplex.nedges(), + )) + } +} + +impl Swap for Children { + fn swap(&mut self, other: &Operator) -> Option> { + match other { + Operator::Transpose(val) => self.swap(val), + Operator::Take(val) => self.swap(val), + Operator::Children(val) => self.swap(val), + Operator::Edges(val) => self.swap(val), + _ => None, + } + } +} + +impl Swap for Edges { + fn swap(&mut self, other: &Operator) -> Option> { + match other { + Operator::Transpose(val) => self.swap(val), + Operator::Take(val) => self.swap(val), + Operator::Children(val) => self.swap(val), + Operator::Edges(val) => self.swap(val), + _ => None, + } + } +} + /// A chain of [`Operator`]s. #[derive(Debug, Clone)] pub struct Chain { From 8ef0be894b5f31e1a3179321194bba4ff3524e67 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Thu, 2 Jun 2022 12:23:55 +0200 Subject: [PATCH 04/45] WIP --- src/chain.rs | 450 ++++++++++++++++++++------------------------------- 1 file changed, 172 insertions(+), 278 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index b8b90fd58..440d96110 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,6 +1,5 @@ use crate::simplex::Simplex; use crate::types::{Dim, Index}; -use std::cmp::Ordering; use std::iter; use std::ops::Mul; @@ -16,7 +15,9 @@ trait SequenceTransformation: Clone { /// Returns the length of the output sequence given the length of the input sequence. fn len(&self, parent_len: Index) -> Index; /// Increment the offset of the coordinate transformation, if applicable. - fn increment_offset(&mut self, increment: Dim); + fn increment_offset(&mut self, amount: Dim); + /// Increment the offset of the coordinate transformation, if applicable. + fn decrement_offset(&mut self, amount: Dim); /// Map the index and coordinate of an element in the output sequence to /// the input sequence. The index is returned, the coordinate is adjusted /// in-place. If the coordinate dimension of the input sequence is larger @@ -52,9 +53,7 @@ trait SequenceTransformation: Clone { let delta_dim = self.delta_dim(); let to_dim = dim + delta_dim; let mut result = Vec::with_capacity(ncoords * to_dim as usize); - let mut result_index = 0; for coord in coordinates.chunks(dim as usize) { - let offset = result.len(); result.extend_from_slice(&coord); result.extend(iter::repeat(0.0).take(delta_dim as usize)); } @@ -64,55 +63,28 @@ trait SequenceTransformation: Clone { } #[derive(Debug, Clone, PartialEq)] -struct DimSlice { - offset: Dim, - len: Dim, +enum OperatorKind { + Index(Index, Index), + Coordinate(Dim, Dim, Dim, Index), } -impl DimSlice { - fn new(offset: Dim, len: Dim) -> Self { - Self { offset, len } +trait DescribeOperator { + fn operator_kind(&self) -> OperatorKind; + #[inline] + fn as_children(&self) -> Option<&Children> { + None } - fn overlaps(&self, other: &DimSlice) -> bool { - match self.partial_cmp(other) { - Some(Ordering::Equal) | None => true, - Some(Ordering::Less) | Some(Ordering::Greater) => false, - } + #[inline] + fn as_children_mut(&mut self) -> Option<&mut Children> { + None } -} - -impl PartialOrd for DimSlice { - fn partial_cmp(&self, other: &Self) -> Option { - if self.offset + self.len <= other.offset { - Some(Ordering::Less) - } else if other.offset + other.len <= self.offset { - Some(Ordering::Greater) - } else if self == other { - Some(Ordering::Equal) - } else { - None - } + #[inline] + fn as_edges(&self) -> Option<&Edges> { + None } -} - -trait InputDimSlice { - fn input_dim_slice(&self) -> DimSlice; -} - -trait OutputDimSlice { - fn output_dim_slice(&self) -> DimSlice; -} - -fn compare_dimensions(left: &impl InputDimSlice, right: &impl OutputDimSlice) -> Option { - let left = left.input_dim_slice(); - let right = right.output_dim_slice(); - left.partial_cmp(&right) -} - -fn dimensions_overlap(left: &impl InputDimSlice, right: &impl OutputDimSlice) -> bool { - match compare_dimensions(left, right) { - Some(Ordering::Equal) | None => true, - Some(Ordering::Less) | Some(Ordering::Greater) => false, + #[inline] + fn as_edges_mut(&mut self) -> Option<&mut Edges> { + None } } @@ -139,7 +111,9 @@ impl SequenceTransformation for Transpose { parent_len } #[inline] - fn increment_offset(&mut self, _increment: Dim) {} + fn increment_offset(&mut self, _amount: Dim) {} + #[inline] + fn decrement_offset(&mut self, _amount: Dim) {} #[inline] fn apply_inplace(&self, index: Index, _coordinate: &mut [f64]) -> Index { let low2 = index % self.len2; @@ -149,6 +123,13 @@ impl SequenceTransformation for Transpose { } } +impl DescribeOperator for Transpose { + #[inline] + fn operator_kind(&self) -> OperatorKind { + OperatorKind::Index(self.len1 * self.len2, self.len1 * self.len2) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Take { indices: Box<[Index]>, @@ -175,7 +156,9 @@ impl SequenceTransformation for Take { (parent_len / self.len) * self.indices.len() as Index } #[inline] - fn increment_offset(&mut self, _increment: Dim) {} + fn increment_offset(&mut self, _amount: Dim) {} + #[inline] + fn decrement_offset(&mut self, _amount: Dim) {} #[inline] fn apply_inplace(&self, index: Index, _coordinate: &mut [f64]) -> Index { let nindices = self.indices.len() as Index; @@ -185,6 +168,13 @@ impl SequenceTransformation for Take { } } +impl DescribeOperator for Take { + #[inline] + fn operator_kind(&self) -> OperatorKind { + OperatorKind::Index(self.len, self.indices.len() as Index) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub struct Children { simplex: Simplex, @@ -208,8 +198,12 @@ impl SequenceTransformation for Children { parent_len * self.simplex.nchildren() } #[inline] - fn increment_offset(&mut self, increment: Dim) { - self.offset += increment; + fn increment_offset(&mut self, amount: Dim) { + self.offset += amount; + } + #[inline] + fn decrement_offset(&mut self, amount: Dim) { + self.offset -= amount; } #[inline] fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { @@ -218,17 +212,23 @@ impl SequenceTransformation for Children { } } -impl InputDimSlice for Children { +impl DescribeOperator for Children { #[inline] - fn input_dim_slice(&self) -> DimSlice { - DimSlice::new(self.offset, self.simplex.dim()) + fn operator_kind(&self) -> OperatorKind { + OperatorKind::Coordinate( + self.offset, + self.simplex.dim(), + self.simplex.dim(), + self.simplex.nchildren(), + ) + } + #[inline] + fn as_children(&self) -> Option<&Children> { + Some(self) } -} - -impl OutputDimSlice for Children { #[inline] - fn output_dim_slice(&self) -> DimSlice { - DimSlice::new(self.offset, self.simplex.dim()) + fn as_children_mut(&mut self) -> Option<&mut Children> { + Some(self) } } @@ -255,8 +255,12 @@ impl SequenceTransformation for Edges { parent_len * self.simplex.nedges() } #[inline] - fn increment_offset(&mut self, increment: Dim) { - self.offset += increment; + fn increment_offset(&mut self, amount: Dim) { + self.offset += amount; + } + #[inline] + fn decrement_offset(&mut self, amount: Dim) { + self.offset -= amount; } #[inline] fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { @@ -265,17 +269,30 @@ impl SequenceTransformation for Edges { } } -impl InputDimSlice for Edges { +impl DescribeOperator for Edges { + #[inline] + fn operator_kind(&self) -> OperatorKind { + OperatorKind::Coordinate( + self.offset, + self.simplex.dim(), + self.simplex.edge_dim(), + self.simplex.nedges(), + ) + } #[inline] - fn input_dim_slice(&self) -> DimSlice { - DimSlice::new(self.offset, self.simplex.edge_dim()) + fn as_edges(&self) -> Option<&Edges> { + Some(self) + } + #[inline] + fn as_edges_mut(&mut self) -> Option<&mut Edges> { + Some(self) } } -impl OutputDimSlice for Edges { +impl DescribeOperator for UniformPoints { #[inline] - fn output_dim_slice(&self) -> DimSlice { - DimSlice::new(self.offset, self.simplex.dim()) + fn operator_kind(&self) -> OperatorKind { + OperatorKind::Coordinate(self.offset, self.point_dim, 0, self.npoints()) } } @@ -294,6 +311,9 @@ impl UniformPoints { offset, } } + pub fn npoints(&self) -> Index { + (self.points.len() / self.point_dim as usize) as Index + } } impl SequenceTransformation for UniformPoints { @@ -306,8 +326,12 @@ impl SequenceTransformation for UniformPoints { parent_len * (self.points.len() as Index / self.point_dim as Index) } #[inline] - fn increment_offset(&mut self, increment: Dim) { - self.offset += increment; + fn increment_offset(&mut self, amount: Dim) { + self.offset += amount; + } + #[inline] + fn decrement_offset(&mut self, amount: Dim) { + self.offset -= amount; } fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { let point_dim = self.point_dim as usize; @@ -359,33 +383,6 @@ macro_rules! impl_from_for_operator { )*} } -macro_rules! dispatch { - ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { - #[inline] - $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { - match $self { - Operator::Transpose(var) => var.$fn($($arg),*), - Operator::Take(var) => var.$fn($($arg),*), - Operator::Children(var) => var.$fn($($arg),*), - Operator::Edges(var) => var.$fn($($arg),*), - Operator::UniformPoints(var) => var.$fn($($arg),*), - } - } - }; - ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { - #[inline] - $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { - match $self { - Operator::Transpose(var) => var.$fn($($arg),*), - Operator::Take(var) => var.$fn($($arg),*), - Operator::Children(var) => var.$fn($($arg),*), - Operator::Edges(var) => var.$fn($($arg),*), - Operator::UniformPoints(var) => var.$fn($($arg),*), - } - } - }; -} - impl_from_for_operator! {Transpose, Take, Children, Edges, UniformPoints} impl Operator { @@ -410,203 +407,94 @@ impl Operator { UniformPoints::new(points, point_dim, offset).into() } pub fn swap(&self, other: &Self) -> Option> { - match (self, other) { - (_, Self::Children(children)) => { - if let Some((ops, children)) = self.swap_with_children(*children) { - Some(iter::once(children.into()).chain(ops.into_iter()).collect()) - } else { - None - } - } - (_, Self::Edges(edges)) => { - if let Some((ops, edges)) = self.swap_with_edges(*edges) { - Some(iter::once(edges.into()).chain(ops.into_iter()).collect()) - } else { - None - } - } - (Self::Transpose(a), Self::Transpose(b)) if a.len1 == b.len2 && a.len2 == b.len1 => { - Some(vec![]) - } - _ => None, - } + let mut other = other.clone(); + swap(self, &mut other).map(|tail| iter::once(other).chain(tail.into_iter()).collect()) } - fn swap_reduce_repeat( - reduce: Operator, - reduce_before: Index, - reduce_after: Index, - nrepeat: Index, - ) -> Vec { - vec![ - Self::new_transpose(nrepeat, reduce_before), - reduce, - Self::new_transpose(reduce_after, nrepeat), - ] - } - pub fn swap_with_children(&self, other: Children) -> Option<(Vec, Children)> { - let mut other = other; - if let Some(tail) = other.swap(self) { - Some((tail, other)) - } else { - None +} + +macro_rules! dispatch { + ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { + match $self { + Operator::Transpose(var) => var.$fn($($arg),*), + Operator::Take(var) => var.$fn($($arg),*), + Operator::Children(var) => var.$fn($($arg),*), + Operator::Edges(var) => var.$fn($($arg),*), + Operator::UniformPoints(var) => var.$fn($($arg),*), + } } - } - pub fn swap_with_edges(&self, other: Edges) -> Option<(Vec, Edges)> { - let mut other = other; - if let Some(tail) = other.swap(self) { - Some((tail, other)) - } else { - None + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { + match $self { + Operator::Transpose(var) => var.$fn($($arg),*), + Operator::Take(var) => var.$fn($($arg),*), + Operator::Children(var) => var.$fn($($arg),*), + Operator::Edges(var) => var.$fn($($arg),*), + Operator::UniformPoints(var) => var.$fn($($arg),*), + } } - } + }; } impl SequenceTransformation for Operator { dispatch! {fn delta_dim(&self) -> Dim} dispatch! {fn len(&self, parent_len: Index) -> Index} - dispatch! {fn increment_offset(&mut self, increment: Dim)} + dispatch! {fn increment_offset(&mut self, amount: Dim)} + dispatch! {fn decrement_offset(&mut self, amount: Dim)} dispatch! {fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index} dispatch! {fn apply_many_inplace(&self, index: Index, coordinates: &mut [f64], dim: Dim) -> Index} dispatch! {fn apply(&self, index: Index, coordinate: &[f64]) -> (Index, Vec)} dispatch! {fn apply_many(&self, index: Index, coordinates: &[f64], dim: Dim) -> (Index, Vec)} } -trait Swap { - fn swap(&mut self, other: &L) -> Option>; -} - -impl Swap for Children { - fn swap(&mut self, other: &Children) -> Option> { - match compare_dimensions(other, self) { - Some(Ordering::Equal) | None => None, - Some(Ordering::Less) | Some(Ordering::Greater) => { - let trans = - Operator::new_transpose(other.simplex.nchildren(), self.simplex.nchildren()); - Some(vec![other.clone().into(), trans]) - } - } - } -} - -impl Swap for Children { - fn swap(&mut self, other: &Edges) -> Option> { - let trans = Operator::new_transpose(other.simplex.nedges(), self.simplex.nchildren()); - match compare_dimensions(other, self) { - Some(Ordering::Less) => { - self.offset += 1; - Some(vec![other.clone().into(), trans]) - } - Some(Ordering::Greater) => Some(vec![other.clone().into(), trans]), - Some(Ordering::Equal) => { - let indices = other.simplex.swap_edges_children_map(); - let take = - Operator::new_take(indices, other.simplex.nchildren() * other.simplex.nedges()); - self.simplex = other.simplex; - Some(vec![other.clone().into(), take]) - } - None => None, - } - } -} - -impl Swap for Edges { - fn swap(&mut self, other: &Children) -> Option> { - let trans = Operator::new_transpose(other.simplex.nchildren(), self.simplex.nedges()); - match compare_dimensions(other, self) { - Some(Ordering::Less) => Some(vec![other.clone().into(), trans]), - Some(Ordering::Greater) => { - let mut other = other.clone(); - other.offset -= 1; - Some(vec![other.into(), trans]) - } - Some(Ordering::Equal) | None => None, +impl DescribeOperator for Operator { + dispatch! {fn operator_kind(&self) -> OperatorKind} + dispatch! {fn as_children(&self) -> Option<&Children>} + dispatch! {fn as_children_mut(&mut self) -> Option<&mut Children>} + dispatch! {fn as_edges(&self) -> Option<&Edges>} + dispatch! {fn as_edges_mut(&mut self) -> Option<&mut Edges>} +} + +fn swap(l: &L, r: &mut R) -> Option> +where + L: DescribeOperator + SequenceTransformation + Into, + R: DescribeOperator + SequenceTransformation, +{ + if let (Some(edges), Some(children)) = (l.as_edges(), r.as_children_mut()) { + if edges.offset == children.offset && edges.simplex.edge_dim() == children.simplex.dim() { + let simplex = edges.simplex; + let indices = simplex.swap_edges_children_map(); + let take = Operator::new_take(indices, simplex.nchildren() * simplex.nedges()); + children.simplex = simplex; + return Some(vec![l.clone().into(), take]); } } -} - -impl Swap for Edges { - fn swap(&mut self, other: &Edges) -> Option> { - let trans = Operator::new_transpose(other.simplex.nedges(), self.simplex.nedges()); - match compare_dimensions(other, self) { - Some(Ordering::Less) => { - self.offset += 1; - Some(vec![other.clone().into(), trans]) + use OperatorKind::*; + match (l.operator_kind(), r.operator_kind()) { + (Index(l_nout, l_nin), Coordinate(_, _, _, r_gen)) => Some(vec![ + Operator::new_transpose(r_gen, l_nout), + l.clone().into(), + Operator::new_transpose(l_nin, r_gen), + ]), + (Coordinate(l_off, _, l_nin, l_gen), Coordinate(r_off, r_nout, _, r_gen)) => { + if l_off + l_nin <= r_off { + r.increment_offset(l.delta_dim()); + Some(vec![ + l.clone().into(), + Operator::new_transpose(l_gen, r_gen), + ]) + } else if l_off >= r_off + r_nout { + let mut l = l.clone(); + l.decrement_offset(r.delta_dim()); + Some(vec![l.into(), Operator::new_transpose(l_gen, r_gen)]) + } else { + None } - Some(Ordering::Greater) => { - let mut other = other.clone(); - other.offset -= 1; - Some(vec![other.into(), trans]) - } - Some(Ordering::Equal) | None => None, - } - } -} - -impl Swap for Children { - fn swap(&mut self, other: &Take) -> Option> { - Some(Operator::swap_reduce_repeat( - other.clone().into(), - other.len, - other.indices.len() as Index, - self.simplex.nchildren(), - )) - } -} - -impl Swap for Edges { - fn swap(&mut self, other: &Take) -> Option> { - Some(Operator::swap_reduce_repeat( - other.clone().into(), - other.len, - other.indices.len() as Index, - self.simplex.nedges(), - )) - } -} - -impl Swap for Children { - fn swap(&mut self, other: &Transpose) -> Option> { - Some(Operator::swap_reduce_repeat( - other.clone().into(), - other.len1 * other.len2, - other.len1 * other.len2, - self.simplex.nchildren(), - )) - } -} - -impl Swap for Edges { - fn swap(&mut self, other: &Transpose) -> Option> { - Some(Operator::swap_reduce_repeat( - other.clone().into(), - other.len1 * other.len2, - other.len1 * other.len2, - self.simplex.nedges(), - )) - } -} - -impl Swap for Children { - fn swap(&mut self, other: &Operator) -> Option> { - match other { - Operator::Transpose(val) => self.swap(val), - Operator::Take(val) => self.swap(val), - Operator::Children(val) => self.swap(val), - Operator::Edges(val) => self.swap(val), - _ => None, - } - } -} - -impl Swap for Edges { - fn swap(&mut self, other: &Operator) -> Option> { - match other { - Operator::Transpose(val) => self.swap(val), - Operator::Take(val) => self.swap(val), - Operator::Children(val) => self.swap(val), - Operator::Edges(val) => self.swap(val), - _ => None, } + _ => None, } } @@ -688,9 +576,15 @@ impl SequenceTransformation for Chain { .rfold(parent_len, |len, op| op.len(len)) } #[inline] - fn increment_offset(&mut self, increment: Dim) { + fn increment_offset(&mut self, amount: Dim) { + for op in self.rev_operators.iter_mut() { + op.increment_offset(amount); + } + } + #[inline] + fn decrement_offset(&mut self, amount: Dim) { for op in self.rev_operators.iter_mut() { - op.increment_offset(increment); + op.decrement_offset(amount); } } #[inline] From 1fb493618c763925cdde0ab994c860628248fd17 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Thu, 2 Jun 2022 14:58:38 +0200 Subject: [PATCH 05/45] wip --- src/chain.rs | 150 ++++++++++++++++++++++++++++++++++++++++++------- src/simplex.rs | 2 +- 2 files changed, 132 insertions(+), 20 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 440d96110..af4f56ca9 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,12 +1,15 @@ use crate::simplex::Simplex; use crate::types::{Dim, Index}; +use std::cmp::Ordering; +use std::collections::BTreeMap; use std::iter; use std::ops::Mul; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum Shape { Point, Simplex(Simplex), + Other, } trait SequenceTransformation: Clone { @@ -59,7 +62,7 @@ trait SequenceTransformation: Clone { } (self.apply_many_inplace(index, &mut result, to_dim), result) } - //fn shape(&self, shape: Shape, offset: Dim) -> Option; + //fn shape(&self, shape: Shape, offset: Dim) -> (Shape, Dim); } #[derive(Debug, Clone, PartialEq)] @@ -69,6 +72,7 @@ enum OperatorKind { } trait DescribeOperator { + /// Returns the kind of operation the operator applies to its parent sequence. fn operator_kind(&self) -> OperatorKind; #[inline] fn as_children(&self) -> Option<&Children> { @@ -88,7 +92,7 @@ trait DescribeOperator { } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Transpose { len1: Index, len2: Index, @@ -130,7 +134,7 @@ impl DescribeOperator for Transpose { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Take { indices: Box<[Index]>, len: Index, @@ -175,7 +179,7 @@ impl DescribeOperator for Take { } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Children { simplex: Simplex, offset: Dim, @@ -232,7 +236,7 @@ impl DescribeOperator for Children { } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Edges { simplex: Simplex, offset: Dim, @@ -289,22 +293,33 @@ impl DescribeOperator for Edges { } } -impl DescribeOperator for UniformPoints { - #[inline] - fn operator_kind(&self) -> OperatorKind { - OperatorKind::Coordinate(self.offset, self.point_dim, 0, self.npoints()) +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +#[repr(transparent)] +struct FiniteF64(pub f64); + +impl Eq for FiniteF64 {} + +impl Ord for FiniteF64 { + fn cmp(&self, other: &Self) -> Ordering { + if let Some(ord) = self.0.partial_cmp(&other.0) { + ord + } else { + panic!("not finite"); + } } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct UniformPoints { - points: Box<[f64]>, + points: Box<[FiniteF64]>, point_dim: Dim, offset: Dim, } impl UniformPoints { pub fn new(points: Box<[f64]>, point_dim: Dim, offset: Dim) -> Self { + // TODO: assert that the points are actually finite. + let points: Box<[FiniteF64]> = unsafe { std::mem::transmute(points) }; Self { points, point_dim, @@ -340,11 +355,20 @@ impl SequenceTransformation for UniformPoints { let npoints = (self.points.len() / point_dim) as Index; let ipoint = index % npoints; let offset = ipoint as usize * point_dim; - coordinate[..point_dim].copy_from_slice(&self.points[offset..offset + point_dim]); + let points: &[f64] = + unsafe { std::mem::transmute(&self.points[offset..offset + point_dim]) }; + coordinate[..point_dim].copy_from_slice(points); index / npoints } } +impl DescribeOperator for UniformPoints { + #[inline] + fn operator_kind(&self) -> OperatorKind { + OperatorKind::Coordinate(self.offset, self.point_dim, 0, self.npoints()) + } +} + /// An operator that maps a sequence of elements to another sequence of elements. /// /// Given a sequence of elements an [`Operator`] defines a new sequence. For @@ -356,7 +380,7 @@ impl SequenceTransformation for UniformPoints { /// and [`Operator::UniformPoints`], or to consecutive chunks of the input /// sequence, in which case the size of the chunks is included in the variant /// and the input sequence is assumed to be a multiple of the chunk size long. -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum Operator { /// The transpose of a sequence: the input sequence is reshaped to `(_, /// len1, len2)`, the last two axes are swapped and the result is @@ -458,6 +482,10 @@ impl DescribeOperator for Operator { dispatch! {fn as_edges_mut(&mut self) -> Option<&mut Edges>} } +impl std::fmt::Debug for Operator { + dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result} +} + fn swap(l: &L, r: &mut R) -> Option> where L: DescribeOperator + SequenceTransformation + Into, @@ -499,7 +527,7 @@ where } /// A chain of [`Operator`]s. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Chain { rev_operators: Vec, } @@ -530,21 +558,70 @@ impl Chain { pub fn iter_operators(&self) -> impl Iterator + DoubleEndedIterator { self.rev_operators.iter().rev() } + fn split_heads(&self) -> BTreeMap> { + let mut heads = BTreeMap::new(); + 'a: for (i, head) in self.rev_operators.iter().enumerate() { + let mut rev_tail: Vec<_> = self.rev_operators.iter().take(i).cloned().collect(); + let mut head = head.clone(); + for op in self.rev_operators.iter().skip(i + 1) { + if let Some(ops) = swap(op, &mut head) { + rev_tail.extend(ops.into_iter().rev()); + } else { + continue 'a; + } + } + heads.insert(head, rev_tail); + } + 'b: for (i, op) in self.rev_operators.iter().enumerate() { + if let Operator::Edges(Edges { + simplex: Simplex::Line, + offset, + }) = op + { + let simplex = Simplex::Line; + let mut rev_tail: Vec<_> = self.rev_operators.iter().take(i).cloned().collect(); + let mut head = Operator::new_children(simplex, *offset); + let indices = simplex.swap_edges_children_map(); + let take = Operator::new_take(indices, simplex.nchildren() * simplex.nedges()); + rev_tail.push(take); + rev_tail.push(op.clone()); + for op in self.rev_operators.iter().skip(i + 1) { + if let Some(ops) = swap(op, &mut head) { + rev_tail.extend(ops.into_iter().rev()); + } else { + continue 'b; + } + } + heads.insert(head, rev_tail); + } + } + heads + } /// Remove and return the common prefix of two chains, transforming either if necessary. pub fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { let mut common = Vec::new(); let mut rev_a = self.rev_operators.clone(); let mut rev_b = other.rev_operators.clone(); + let mut i = 0; while !rev_a.is_empty() && !rev_b.is_empty() { + i += 1; + if i > 10 { + break; + } if rev_a.last() == rev_b.last() { common.push(rev_a.pop().unwrap()); rev_b.pop(); continue; } - // [Edges(Line)], [Children(Line)] -> [Children(Line), Take, Edges(Line)], [Children(Line)] - // List all edges and children that can be moved to the front - // and remove the common operator with the least amount of - // operators in front. + let heads_a = Chain::new(rev_a.iter().rev().cloned()).split_heads(); + let heads_b = Chain::new(rev_b.iter().rev().cloned()).split_heads(); + let candidates: Vec<_> = heads_a.iter().filter_map(|(h, a)| heads_b.get(h).map(|b| (h, a, b))).collect(); + if let Some((head, a, b)) = candidates.into_iter().min_by_key(|(_, a, b)| std::cmp::max(a.len(), b.len())) { + common.push(head.clone()); + rev_a = a.clone(); + rev_b = b.clone(); + continue; + } break; } let common = if rev_a.is_empty() @@ -959,4 +1036,39 @@ mod tests { Operator::new_edges(Line, 2), Operator::new_edges(Triangle, 0), } + + #[test] + fn split_heads() { + let chain = Chain::new([ + Operator::new_edges(Triangle, 1), + Operator::new_children(Line, 0), + Operator::new_edges(Line, 2), + Operator::new_children(Line, 1), + Operator::new_children(Line, 0), + ]); + let desired = chain + .iter_operators() + .cloned() + .fold(Topology::new(4, 1), |topo, op| topo.derive(op)); + for (head, tail) in chain.split_heads().into_iter() { + let actual = iter::once(head) + .chain(tail.into_iter().rev()) + .fold(Topology::new(4, 1), |topo, op| topo.derive(op)); + assert_equiv_topo!(actual, desired, Line, Line); + } + } + + #[test] + fn remove_common_prefix() { + let a = Chain::new([Operator::new_children(Line, 0), Operator::new_children(Line, 0)]); + let b = Chain::new([Operator::new_edges(Line, 0)]); + assert_eq!( + a.remove_common_prefix(&b), + ( + Chain::new([Operator::new_children(Line, 0), Operator::new_children(Line, 0)]), + Chain::new([]), + Chain::new([Operator::new_edges(Line, 0), Operator::new_take([2, 1], 4), Operator::new_take([2, 1], 4)]), + ) + ); + } } diff --git a/src/simplex.rs b/src/simplex.rs index a828a0ffa..ad0694c69 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -1,7 +1,7 @@ use crate::types::{Dim, Index}; /// Simplex. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Simplex { Line, Triangle, From 8b7098178cc3bdc21c15e40c00fc82e32cabba47 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Fri, 3 Jun 2022 11:28:15 +0200 Subject: [PATCH 06/45] WIP --- src/chain.rs | 92 +++++++++++++++++++++++++------------------------- src/simplex.rs | 29 ++++++++-------- src/types.rs | 1 - 3 files changed, 60 insertions(+), 62 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index af4f56ca9..4c99eaf0c 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,5 +1,5 @@ use crate::simplex::Simplex; -use crate::types::{Dim, Index}; +use crate::types::Dim; use std::cmp::Ordering; use std::collections::BTreeMap; use std::iter; @@ -16,7 +16,7 @@ trait SequenceTransformation: Clone { /// Returns the difference between the dimension of the input and the output sequence. fn delta_dim(&self) -> Dim; /// Returns the length of the output sequence given the length of the input sequence. - fn len(&self, parent_len: Index) -> Index; + fn len(&self, parent_len: usize) -> usize; /// Increment the offset of the coordinate transformation, if applicable. fn increment_offset(&mut self, amount: Dim); /// Increment the offset of the coordinate transformation, if applicable. @@ -26,11 +26,11 @@ trait SequenceTransformation: Clone { /// in-place. If the coordinate dimension of the input sequence is larger /// than that of the output sequence, the [Operator::delta_dim()] last /// elements of the coordinate are discarded. - fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index; + fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize; /// Map the index and multiple coordinates of an element in the output /// sequence to the input sequence. The index is returned, the coordinates /// are adjusted in-place. - fn apply_many_inplace(&self, index: Index, coordinates: &mut [f64], dim: Dim) -> Index { + fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: Dim) -> usize { let dim = dim as usize; let mut result_index = 0; for i in 0..coordinates.len() / dim { @@ -40,7 +40,7 @@ trait SequenceTransformation: Clone { } /// Map the index and coordinate of an element in the output sequence to /// the input sequence. - fn apply(&self, index: Index, coordinate: &[f64]) -> (Index, Vec) { + fn apply(&self, index: usize, coordinate: &[f64]) -> (usize, Vec) { let delta_dim = self.delta_dim() as usize; let to_dim = coordinate.len() + delta_dim; let mut result = Vec::with_capacity(to_dim); @@ -50,7 +50,7 @@ trait SequenceTransformation: Clone { } /// Map the index and multiple coordinates of an element in the output /// sequence to the input sequence. - fn apply_many(&self, index: Index, coordinates: &[f64], dim: Dim) -> (Index, Vec) { + fn apply_many(&self, index: usize, coordinates: &[f64], dim: Dim) -> (usize, Vec) { assert_eq!(coordinates.len() % dim as usize, 0); let ncoords = coordinates.len() / dim as usize; let delta_dim = self.delta_dim(); @@ -67,8 +67,8 @@ trait SequenceTransformation: Clone { #[derive(Debug, Clone, PartialEq)] enum OperatorKind { - Index(Index, Index), - Coordinate(Dim, Dim, Dim, Index), + Index(usize, usize), + Coordinate(Dim, Dim, Dim, usize), } trait DescribeOperator { @@ -94,13 +94,13 @@ trait DescribeOperator { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Transpose { - len1: Index, - len2: Index, + len1: usize, + len2: usize, } impl Transpose { #[inline] - pub fn new(len1: Index, len2: Index) -> Self { + pub fn new(len1: usize, len2: usize) -> Self { Self { len1, len2 } } } @@ -111,7 +111,7 @@ impl SequenceTransformation for Transpose { 0 } #[inline] - fn len(&self, parent_len: Index) -> Index { + fn len(&self, parent_len: usize) -> usize { parent_len } #[inline] @@ -119,7 +119,7 @@ impl SequenceTransformation for Transpose { #[inline] fn decrement_offset(&mut self, _amount: Dim) {} #[inline] - fn apply_inplace(&self, index: Index, _coordinate: &mut [f64]) -> Index { + fn apply_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { let low2 = index % self.len2; let low1 = (index / self.len2) % self.len1; let high = index / (self.len1 * self.len2); @@ -136,13 +136,13 @@ impl DescribeOperator for Transpose { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Take { - indices: Box<[Index]>, - len: Index, + indices: Box<[usize]>, + len: usize, } impl Take { #[inline] - pub fn new(indices: impl Into>, len: Index) -> Self { + pub fn new(indices: impl Into>, len: usize) -> Self { Self { indices: indices.into(), len, @@ -156,26 +156,26 @@ impl SequenceTransformation for Take { 0 } #[inline] - fn len(&self, parent_len: Index) -> Index { - (parent_len / self.len) * self.indices.len() as Index + fn len(&self, parent_len: usize) -> usize { + (parent_len / self.len) * self.indices.len() } #[inline] fn increment_offset(&mut self, _amount: Dim) {} #[inline] fn decrement_offset(&mut self, _amount: Dim) {} #[inline] - fn apply_inplace(&self, index: Index, _coordinate: &mut [f64]) -> Index { - let nindices = self.indices.len() as Index; + fn apply_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { + let nindices = self.indices.len(); let low = index % nindices; let high = index / nindices; - high * self.len + self.indices[low as usize] + high * self.len + self.indices[low] } } impl DescribeOperator for Take { #[inline] fn operator_kind(&self) -> OperatorKind { - OperatorKind::Index(self.len, self.indices.len() as Index) + OperatorKind::Index(self.len, self.indices.len()) } } @@ -198,7 +198,7 @@ impl SequenceTransformation for Children { 0 } #[inline] - fn len(&self, parent_len: Index) -> Index { + fn len(&self, parent_len: usize) -> usize { parent_len * self.simplex.nchildren() } #[inline] @@ -210,7 +210,7 @@ impl SequenceTransformation for Children { self.offset -= amount; } #[inline] - fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { self.simplex .apply_child_inplace(index, &mut coordinate[self.offset as usize..]) } @@ -255,7 +255,7 @@ impl SequenceTransformation for Edges { 1 } #[inline] - fn len(&self, parent_len: Index) -> Index { + fn len(&self, parent_len: usize) -> usize { parent_len * self.simplex.nedges() } #[inline] @@ -267,7 +267,7 @@ impl SequenceTransformation for Edges { self.offset -= amount; } #[inline] - fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { self.simplex .apply_edge_inplace(index, &mut coordinate[self.offset as usize..]) } @@ -326,8 +326,8 @@ impl UniformPoints { offset, } } - pub fn npoints(&self) -> Index { - (self.points.len() / self.point_dim as usize) as Index + pub fn npoints(&self) -> usize { + self.points.len() / self.point_dim as usize } } @@ -337,8 +337,8 @@ impl SequenceTransformation for UniformPoints { self.point_dim } #[inline] - fn len(&self, parent_len: Index) -> Index { - parent_len * (self.points.len() as Index / self.point_dim as Index) + fn len(&self, parent_len: usize) -> usize { + parent_len * (self.points.len() / self.point_dim as usize) } #[inline] fn increment_offset(&mut self, amount: Dim) { @@ -348,11 +348,11 @@ impl SequenceTransformation for UniformPoints { fn decrement_offset(&mut self, amount: Dim) { self.offset -= amount; } - fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { let point_dim = self.point_dim as usize; let coordinate = &mut coordinate[self.offset as usize..]; coordinate.copy_within(..coordinate.len() - point_dim, point_dim); - let npoints = (self.points.len() / point_dim) as Index; + let npoints = self.points.len() / point_dim; let ipoint = index % npoints; let offset = ipoint as usize * point_dim; let points: &[f64] = @@ -411,11 +411,11 @@ impl_from_for_operator! {Transpose, Take, Children, Edges, UniformPoints} impl Operator { /// Construct a new operator that transposes a sequence of elements. - pub fn new_transpose(len1: Index, len2: Index) -> Self { + pub fn new_transpose(len1: usize, len2: usize) -> Self { Transpose::new(len1, len2).into() } /// Construct a new operator that takes a subset of a sequence of elements. - pub fn new_take(indices: impl Into>, len: Index) -> Self { + pub fn new_take(indices: impl Into>, len: usize) -> Self { Take::new(indices, len).into() } /// Construct a new operator that maps a sequence of elements to its children. @@ -465,13 +465,13 @@ macro_rules! dispatch { impl SequenceTransformation for Operator { dispatch! {fn delta_dim(&self) -> Dim} - dispatch! {fn len(&self, parent_len: Index) -> Index} + dispatch! {fn len(&self, parent_len: usize) -> usize} dispatch! {fn increment_offset(&mut self, amount: Dim)} dispatch! {fn decrement_offset(&mut self, amount: Dim)} - dispatch! {fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index} - dispatch! {fn apply_many_inplace(&self, index: Index, coordinates: &mut [f64], dim: Dim) -> Index} - dispatch! {fn apply(&self, index: Index, coordinate: &[f64]) -> (Index, Vec)} - dispatch! {fn apply_many(&self, index: Index, coordinates: &[f64], dim: Dim) -> (Index, Vec)} + dispatch! {fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize} + dispatch! {fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: Dim) -> usize} + dispatch! {fn apply(&self, index: usize, coordinate: &[f64]) -> (usize, Vec)} + dispatch! {fn apply_many(&self, index: usize, coordinates: &[f64], dim: Dim) -> (usize, Vec)} } impl DescribeOperator for Operator { @@ -647,7 +647,7 @@ impl SequenceTransformation for Chain { self.rev_operators.iter().map(|op| op.delta_dim()).sum() } #[inline] - fn len(&self, parent_len: Index) -> Index { + fn len(&self, parent_len: usize) -> usize { self.rev_operators .iter() .rfold(parent_len, |len, op| op.len(len)) @@ -665,13 +665,13 @@ impl SequenceTransformation for Chain { } } #[inline] - fn apply_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { self.rev_operators .iter() .fold(index, |index, op| op.apply_inplace(index, coordinate)) } #[inline] - fn apply_many_inplace(&self, index: Index, coordinates: &mut [f64], dim: Dim) -> Index { + fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: Dim) -> usize { self.rev_operators.iter().fold(index, |index, op| { op.apply_many_inplace(index, coordinates, dim) }) @@ -679,18 +679,18 @@ impl SequenceTransformation for Chain { } //#[derive(Debug, Clone)] -//pub struct ConcatChain(Vec<(Chain, Index)>); +//pub struct ConcatChain(Vec<(Chain, usize)>); #[derive(Debug, Clone)] pub struct Topology { transforms: Chain, dim: Dim, - root_len: Index, - len: Index, + root_len: usize, + len: usize, } impl Topology { - pub fn new(dim: Dim, len: Index) -> Self { + pub fn new(dim: Dim, len: usize) -> Self { Self { transforms: Chain::new([]), dim, diff --git a/src/simplex.rs b/src/simplex.rs index ad0694c69..c42278777 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -1,4 +1,4 @@ -use crate::types::{Dim, Index}; +use crate::types::Dim; /// Simplex. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -23,7 +23,7 @@ impl Simplex { } /// Returns the number of edges of the simplex. #[inline] - pub const fn nedges(&self) -> Index { + pub const fn nedges(&self) -> usize { match self { Self::Line => 2, Self::Triangle => 3, @@ -31,27 +31,27 @@ impl Simplex { } /// Returns the number of children of the simplex. #[inline] - pub const fn nchildren(&self) -> Index { + pub const fn nchildren(&self) -> usize { match self { Self::Line => 2, Self::Triangle => 4, } } - const LINE_SWAP_EDGES_CHILDREN_MAP: [Index; 2] = [2, 1]; - const TRIANGLE_SWAP_EDGES_CHILDREN_MAP: [Index; 6] = [3, 6, 1, 7, 2, 5]; + const LINE_SWAP_EDGES_CHILDREN_MAP: [usize; 2] = [2, 1]; + const TRIANGLE_SWAP_EDGES_CHILDREN_MAP: [usize; 6] = [3, 6, 1, 7, 2, 5]; /// Returns an array of indices of edges of children corresponding to children of edges. #[inline] - pub const fn swap_edges_children_map(&self) -> &'static [Index] { + pub const fn swap_edges_children_map(&self) -> &'static [usize] { match self { Self::Line => &Self::LINE_SWAP_EDGES_CHILDREN_MAP, Self::Triangle => &Self::TRIANGLE_SWAP_EDGES_CHILDREN_MAP, } } - const LINE_CONNECTIVITY: [Option; 4] = [Some(3), None, None, Some(0)]; - const TRIANGLE_CONNECTIVITY: [Option; 12] = [ + const LINE_CONNECTIVITY: [Option; 4] = [Some(3), None, None, Some(0)]; + const TRIANGLE_CONNECTIVITY: [Option; 12] = [ Some(11), None, None, @@ -69,7 +69,7 @@ impl Simplex { /// Returns an array of indices of opposite edges of children, or `None` if /// an edge lies on the boundary of the simplex. #[inline] - pub const fn connectivity(&self) -> &'static [Option] { + pub const fn connectivity(&self) -> &'static [Option] { match self { Self::Line => &Self::LINE_CONNECTIVITY, Self::Triangle => &Self::TRIANGLE_CONNECTIVITY, @@ -91,7 +91,7 @@ impl Simplex { /// Transform the given child `coordinate` for child `index` to this parent /// simplex in-place. The returned index is the index of the parent in an /// infinite, uniform sequence. - pub fn apply_child_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + pub fn apply_child_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { match self { Self::Line => { coordinate[0] = 0.5 * (coordinate[0] + (index % 2) as f64); @@ -120,7 +120,7 @@ impl Simplex { /// Transform the given edge `coordinate` for edge `index` to this parent /// simplex in-place. The returned index is the index of the parent in an /// infinite, uniform sequence. - pub fn apply_edge_inplace(&self, index: Index, coordinate: &mut [f64]) -> Index { + pub fn apply_edge_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { coordinate.copy_within( self.edge_dim() as usize..coordinate.len() - 1, self.dim() as usize, @@ -248,7 +248,7 @@ mod tests { for (i, j) in Line.swap_edges_children_map().iter().cloned().enumerate() { let mut x = [0.5]; let mut y = [0.5]; - Line.apply_edge_inplace(i as Index, &mut x); + Line.apply_edge_inplace(i, &mut x); Line.apply_child_inplace(Line.apply_edge_inplace(j, &mut y), &mut y); assert_abs_diff_eq!(x[..], y[..]); } @@ -264,7 +264,7 @@ mod tests { { let mut x = [0.5, 0.5]; let mut y = [0.5, 0.5]; - Triangle.apply_edge_inplace(Line.apply_child_inplace(i as Index, &mut x), &mut x); + Triangle.apply_edge_inplace(Line.apply_child_inplace(i, &mut x), &mut x); Triangle.apply_child_inplace(Triangle.apply_edge_inplace(j, &mut y), &mut y); assert_abs_diff_eq!(x[..], y[..]); } @@ -276,7 +276,7 @@ mod tests { if let Some(j) = j { let mut x = [0.5]; let mut y = [0.5]; - Line.apply_child_inplace(Line.apply_edge_inplace(i as Index, &mut x), &mut x); + Line.apply_child_inplace(Line.apply_edge_inplace(i, &mut x), &mut x); Line.apply_child_inplace(Line.apply_edge_inplace(j, &mut y), &mut y); assert_abs_diff_eq!(x[..], y[..]); } @@ -287,7 +287,6 @@ mod tests { fn triangle_connectivity() { for (i, j) in Triangle.connectivity().iter().cloned().enumerate() { if let Some(j) = j { - let i = i as Index; let mut x = [0.5, 0.5]; let mut y = [0.5, 0.5]; Triangle.apply_child_inplace(Triangle.apply_edge_inplace(i, &mut x), &mut x); diff --git a/src/types.rs b/src/types.rs index a82167ffc..a1ca39912 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,2 @@ pub type Dim = u8; -pub type Index = u64; From 988ff5d21e2e29ca112dc63f31595361c6b85acc Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Fri, 3 Jun 2022 14:35:19 +0200 Subject: [PATCH 07/45] WIP --- src/chain.rs | 207 ++++++++++++++++++++++++++++++++++++++++++------- src/simplex.rs | 12 +++ 2 files changed, 191 insertions(+), 28 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 4c99eaf0c..fedabfd0e 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -21,12 +21,14 @@ trait SequenceTransformation: Clone { fn increment_offset(&mut self, amount: Dim); /// Increment the offset of the coordinate transformation, if applicable. fn decrement_offset(&mut self, amount: Dim); + /// Map the index. + fn apply_index(&self, index: usize) -> usize; /// Map the index and coordinate of an element in the output sequence to /// the input sequence. The index is returned, the coordinate is adjusted /// in-place. If the coordinate dimension of the input sequence is larger /// than that of the output sequence, the [Operator::delta_dim()] last /// elements of the coordinate are discarded. - fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize; + fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize; /// Map the index and multiple coordinates of an element in the output /// sequence to the input sequence. The index is returned, the coordinates /// are adjusted in-place. @@ -34,19 +36,19 @@ trait SequenceTransformation: Clone { let dim = dim as usize; let mut result_index = 0; for i in 0..coordinates.len() / dim { - result_index = self.apply_inplace(index, &mut coordinates[i * dim..(i + 1) * dim]); + result_index = self.apply_one_inplace(index, &mut coordinates[i * dim..(i + 1) * dim]); } result_index } /// Map the index and coordinate of an element in the output sequence to /// the input sequence. - fn apply(&self, index: usize, coordinate: &[f64]) -> (usize, Vec) { + fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec) { let delta_dim = self.delta_dim() as usize; let to_dim = coordinate.len() + delta_dim; let mut result = Vec::with_capacity(to_dim); result.extend_from_slice(coordinate); result.extend(iter::repeat(0.0).take(delta_dim)); - (self.apply_inplace(index, &mut result), result) + (self.apply_one_inplace(index, &mut result), result) } /// Map the index and multiple coordinates of an element in the output /// sequence to the input sequence. @@ -119,12 +121,20 @@ impl SequenceTransformation for Transpose { #[inline] fn decrement_offset(&mut self, _amount: Dim) {} #[inline] - fn apply_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { + fn apply_index(&self, index: usize) -> usize { let low2 = index % self.len2; let low1 = (index / self.len2) % self.len1; let high = index / (self.len1 * self.len2); high * (self.len1 * self.len2) + low2 * self.len1 + low1 } + #[inline] + fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { + self.apply_index(index) + } + #[inline] + fn apply_many_inplace(&self, index: usize, _coordinate: &mut [f64], _dim: Dim) -> usize { + self.apply_index(index) + } } impl DescribeOperator for Transpose { @@ -164,12 +174,20 @@ impl SequenceTransformation for Take { #[inline] fn decrement_offset(&mut self, _amount: Dim) {} #[inline] - fn apply_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { + fn apply_index(&self, index: usize) -> usize { let nindices = self.indices.len(); let low = index % nindices; let high = index / nindices; high * self.len + self.indices[low] } + #[inline] + fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { + self.apply_index(index) + } + #[inline] + fn apply_many_inplace(&self, index: usize, _coordinate: &mut [f64], _dim: Dim) -> usize { + self.apply_index(index) + } } impl DescribeOperator for Take { @@ -210,7 +228,11 @@ impl SequenceTransformation for Children { self.offset -= amount; } #[inline] - fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { + fn apply_index(&self, index: usize) -> usize { + self.simplex.apply_child_index(index) + } + #[inline] + fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { self.simplex .apply_child_inplace(index, &mut coordinate[self.offset as usize..]) } @@ -267,7 +289,11 @@ impl SequenceTransformation for Edges { self.offset -= amount; } #[inline] - fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { + fn apply_index(&self, index: usize) -> usize { + self.simplex.apply_edge_index(index) + } + #[inline] + fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { self.simplex .apply_edge_inplace(index, &mut coordinate[self.offset as usize..]) } @@ -326,7 +352,8 @@ impl UniformPoints { offset, } } - pub fn npoints(&self) -> usize { + #[inline] + pub const fn npoints(&self) -> usize { self.points.len() / self.point_dim as usize } } @@ -338,7 +365,7 @@ impl SequenceTransformation for UniformPoints { } #[inline] fn len(&self, parent_len: usize) -> usize { - parent_len * (self.points.len() / self.point_dim as usize) + parent_len * self.npoints() } #[inline] fn increment_offset(&mut self, amount: Dim) { @@ -348,17 +375,20 @@ impl SequenceTransformation for UniformPoints { fn decrement_offset(&mut self, amount: Dim) { self.offset -= amount; } - fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { + #[inline] + fn apply_index(&self, index: usize) -> usize { + index / self.npoints() + } + fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { let point_dim = self.point_dim as usize; let coordinate = &mut coordinate[self.offset as usize..]; coordinate.copy_within(..coordinate.len() - point_dim, point_dim); - let npoints = self.points.len() / point_dim; - let ipoint = index % npoints; + let ipoint = index % self.npoints(); let offset = ipoint as usize * point_dim; let points: &[f64] = unsafe { std::mem::transmute(&self.points[offset..offset + point_dim]) }; coordinate[..point_dim].copy_from_slice(points); - index / npoints + index / self.npoints() } } @@ -468,9 +498,10 @@ impl SequenceTransformation for Operator { dispatch! {fn len(&self, parent_len: usize) -> usize} dispatch! {fn increment_offset(&mut self, amount: Dim)} dispatch! {fn decrement_offset(&mut self, amount: Dim)} - dispatch! {fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> usize} + dispatch! {fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize} dispatch! {fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: Dim) -> usize} - dispatch! {fn apply(&self, index: usize, coordinate: &[f64]) -> (usize, Vec)} + dispatch! {fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec)} dispatch! {fn apply_many(&self, index: usize, coordinates: &[f64], dim: Dim) -> (usize, Vec)} } @@ -615,8 +646,14 @@ impl Chain { } let heads_a = Chain::new(rev_a.iter().rev().cloned()).split_heads(); let heads_b = Chain::new(rev_b.iter().rev().cloned()).split_heads(); - let candidates: Vec<_> = heads_a.iter().filter_map(|(h, a)| heads_b.get(h).map(|b| (h, a, b))).collect(); - if let Some((head, a, b)) = candidates.into_iter().min_by_key(|(_, a, b)| std::cmp::max(a.len(), b.len())) { + let candidates: Vec<_> = heads_a + .iter() + .filter_map(|(h, a)| heads_b.get(h).map(|b| (h, a, b))) + .collect(); + if let Some((head, a, b)) = candidates + .into_iter() + .min_by_key(|(_, a, b)| std::cmp::max(a.len(), b.len())) + { common.push(head.clone()); rev_a = a.clone(); rev_b = b.clone(); @@ -665,10 +702,16 @@ impl SequenceTransformation for Chain { } } #[inline] - fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { + fn apply_index(&self, index: usize) -> usize { + self.rev_operators + .iter() + .fold(index, |index, op| op.apply_index(index)) + } + #[inline] + fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { self.rev_operators .iter() - .fold(index, |index, op| op.apply_inplace(index, coordinate)) + .fold(index, |index, op| op.apply_one_inplace(index, coordinate)) } #[inline] fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: Dim) -> usize { @@ -679,7 +722,105 @@ impl SequenceTransformation for Chain { } //#[derive(Debug, Clone)] -//pub struct ConcatChain(Vec<(Chain, usize)>); +//struct AbsoluteChains { +// tip_dim: Dim, +// delta_dim: Dim, +// chains: Vec<(usize, Chain)>, +//} +// +//#[derive(Debug, Clone, PartialEq)] +//enum NewAbsoluteChainsError { +// DeltaDimensionMismatch, +//} +// +//impl std::error::Error for NewAbsoluteChainsError {} +// +//impl std::fmt::Display for NewAbsoluteChainsError { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// std::fmt::Debug::fmt(self, f) +// } +//} +// +//impl AbsoluteChains { +// pub fn new( +// tip_dim: Dim, +// delta_dim: Dim, +// chains: Vec<(usize, Chain)>, +// ) -> Result { +// if chains +// .iter() +// .any(|(_, chain)| chain.delta_dim() != delta_dim) +// { +// Err(NewAbsoluteChainsError::DeltaDimensionMismatch) +// } else { +// Ok(Self { +// tip_dim, +// delta_dim, +// chains, +// }) +// } +// } +// #[inline] +// pub fn len(&self) -> usize { +// self.chains.iter().map(|(len, _)| len).sum() +// } +// #[inline] +// pub fn root_dim(&self) -> Dim { +// self.tip_dim + self.delta_dim +// } +// #[inline] +// pub fn tip_dim(&self) -> Dim { +// self.tip_dim +// } +// fn get_chain(&self, mut index: usize) -> Option<(&Chain, usize)> { +// for (len, chain) in self.chains.iter() { +// if index < *len { +// return Some((&chain, index)); +// } +// index -= len; +// } +// None +// } +// #[inline] +// pub fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> Option { +// self.get_chain(index) +// .map(|(chain, index)| chain.apply_many_inplace(index, coordinate, self.root_dim())) +// } +// #[inline] +// pub fn apply(&self, index: usize, coordinates: &[f64]) -> Option<(usize, Vec)> { +// self.get_chain(index) +// .map(|(chain, index)| chain.apply_many(index, coordinates, self.tip_dim())) +// } +//} +// +//pub struct AbsoluteChain { +// tip_dim: Dim, +// delta_dim: Dim, +// tip_len: usize, +// chain: Chain, +//} +// +//pub struct RelativeChain { +// tip_dim: Dim, +// delta_dim: Dim, +// order: Vec, +// chains: Vec, +//} +// +//impl RelativeChain { +// pub fn get_chain(&self, index: usize) -> Option<(Chain, usize, usize)> { +// for (offset, indices, +// pub fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { +// self.get_chain(index).map(|(chain, offset, index)| { +// offset + chain.apply_many_inplace(index, coordinates, self.tip_dim()) +// }) +// } +// pub fn apply(&self, index: usize, coordinates: &[f64]) -> Option<(usize, Vec)> { +// self.get_chain(index).map(|(chain, offset, index)| { +// offset + chain.apply_many(index, coordinates, self.tip_dim()) +// }) +// } +//} #[derive(Debug, Clone)] pub struct Topology { @@ -750,7 +891,7 @@ mod tests { for i in ic.len()..oc.len() { work[i] = 0.0; } - assert_eq!($op.apply_inplace($ii, &mut work), $oi); + assert_eq!($op.apply_one_inplace($ii, &mut work), $oi); assert_abs_diff_eq!(work[..], oc[..]); }}; } @@ -808,7 +949,7 @@ mod tests { for j in 0..2 { for k in 0..3 { assert_eq!( - op.apply((i * 2 + j) * 3 + k, &[]), + op.apply_one((i * 2 + j) * 3 + k, &[]), ((i * 3 + k) * 2 + j, vec![]) ); } @@ -899,8 +1040,8 @@ mod tests { for i in 0..topo1.len { let ielem = i / (npoints / nelems); assert_eq!( - topo1.transforms.apply_inplace(i, &mut coord1), - topo2.transforms.apply_inplace(i, &mut coord2), + topo1.transforms.apply_one_inplace(i, &mut coord1), + topo2.transforms.apply_one_inplace(i, &mut coord2), "topo1 and topo2 map element {ielem} to different root elements" ); assert_abs_diff_eq!(coord1[..], coord2[..]); @@ -1060,14 +1201,24 @@ mod tests { #[test] fn remove_common_prefix() { - let a = Chain::new([Operator::new_children(Line, 0), Operator::new_children(Line, 0)]); + let a = Chain::new([ + Operator::new_children(Line, 0), + Operator::new_children(Line, 0), + ]); let b = Chain::new([Operator::new_edges(Line, 0)]); assert_eq!( a.remove_common_prefix(&b), ( - Chain::new([Operator::new_children(Line, 0), Operator::new_children(Line, 0)]), + Chain::new([ + Operator::new_children(Line, 0), + Operator::new_children(Line, 0) + ]), Chain::new([]), - Chain::new([Operator::new_edges(Line, 0), Operator::new_take([2, 1], 4), Operator::new_take([2, 1], 4)]), + Chain::new([ + Operator::new_edges(Line, 0), + Operator::new_take([2, 1], 4), + Operator::new_take([2, 1], 4) + ]), ) ); } diff --git a/src/simplex.rs b/src/simplex.rs index c42278777..00a782901 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -117,6 +117,12 @@ impl Simplex { } } } + pub const fn apply_child_index(&self, index: usize) -> usize { + match self { + Self::Line => index / 2, + Self::Triangle => index / 4, + } + } /// Transform the given edge `coordinate` for edge `index` to this parent /// simplex in-place. The returned index is the index of the parent in an /// infinite, uniform sequence. @@ -148,6 +154,12 @@ impl Simplex { } } } + pub const fn apply_edge_index(&self, index: usize) -> usize { + match self { + Self::Line => index / 2, + Self::Triangle => index / 3, + } + } } #[cfg(test)] From 8e1f3d25b71c16fc71fb4a1536bd7bf0fad1ba5e Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 8 Jun 2022 12:32:44 +0200 Subject: [PATCH 08/45] WIP --- src/chain.rs | 301 +++++++++++++++++++++++++++++++++++++----------- src/lib.rs | 1 + src/sequence.rs | 126 ++++++++++++++++++++ src/simplex.rs | 21 ++++ 4 files changed, 382 insertions(+), 67 deletions(-) create mode 100644 src/sequence.rs diff --git a/src/chain.rs b/src/chain.rs index fedabfd0e..a37385761 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -5,24 +5,17 @@ use std::collections::BTreeMap; use std::iter; use std::ops::Mul; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -enum Shape { - Point, - Simplex(Simplex), - Other, -} - -trait SequenceTransformation: Clone { +pub(crate) trait UnsizedSequence: Clone { /// Returns the difference between the dimension of the input and the output sequence. fn delta_dim(&self) -> Dim; - /// Returns the length of the output sequence given the length of the input sequence. - fn len(&self, parent_len: usize) -> usize; - /// Increment the offset of the coordinate transformation, if applicable. + fn delta_len(&self) -> (usize, usize); fn increment_offset(&mut self, amount: Dim); /// Increment the offset of the coordinate transformation, if applicable. fn decrement_offset(&mut self, amount: Dim); /// Map the index. fn apply_index(&self, index: usize) -> usize; + /// Returns all indices that map to the given indices. TODO: examples with take and children? + fn unapply_indices(&self, indices: &[usize]) -> Vec; /// Map the index and coordinate of an element in the output sequence to /// the input sequence. The index is returned, the coordinate is adjusted /// in-place. If the coordinate dimension of the input sequence is larger @@ -59,18 +52,18 @@ trait SequenceTransformation: Clone { let to_dim = dim + delta_dim; let mut result = Vec::with_capacity(ncoords * to_dim as usize); for coord in coordinates.chunks(dim as usize) { - result.extend_from_slice(&coord); + result.extend_from_slice(coord); result.extend(iter::repeat(0.0).take(delta_dim as usize)); } (self.apply_many_inplace(index, &mut result, to_dim), result) } - //fn shape(&self, shape: Shape, offset: Dim) -> (Shape, Dim); } #[derive(Debug, Clone, PartialEq)] enum OperatorKind { Index(usize, usize), Coordinate(Dim, Dim, Dim, usize), + Other, } trait DescribeOperator { @@ -94,6 +87,54 @@ trait DescribeOperator { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Offset(usize); + +impl Offset { + #[inline] + pub fn new(offset: usize) -> Self { + Self(offset) + } +} + +impl UnsizedSequence for Offset { + #[inline] + fn delta_dim(&self) -> Dim { + 0 + } + #[inline] + fn delta_len(&self) -> (usize, usize) { + (1, 1) + } + #[inline] + fn increment_offset(&mut self, _amount: Dim) {} + #[inline] + fn decrement_offset(&mut self, _amount: Dim) {} + #[inline] + fn apply_index(&self, index: usize) -> usize { + index + self.0 + } + #[inline] + fn unapply_indices(&self, indices: &[usize]) -> Vec { + indices.iter().filter_map(|i| i.checked_sub(self.0)).collect() + } + #[inline] + fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { + self.apply_index(index) + } + #[inline] + fn apply_many_inplace(&self, index: usize, _coordinate: &mut [f64], _dim: Dim) -> usize { + self.apply_index(index) + } +} + +impl DescribeOperator for Offset { + #[inline] + fn operator_kind(&self) -> OperatorKind { + OperatorKind::Other + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Transpose { len1: usize, @@ -107,14 +148,14 @@ impl Transpose { } } -impl SequenceTransformation for Transpose { +impl UnsizedSequence for Transpose { #[inline] fn delta_dim(&self) -> Dim { 0 } #[inline] - fn len(&self, parent_len: usize) -> usize { - parent_len + fn delta_len(&self) -> (usize, usize) { + (1, 1) } #[inline] fn increment_offset(&mut self, _amount: Dim) {} @@ -123,9 +164,20 @@ impl SequenceTransformation for Transpose { #[inline] fn apply_index(&self, index: usize) -> usize { let low2 = index % self.len2; - let low1 = (index / self.len2) % self.len1; - let high = index / (self.len1 * self.len2); - high * (self.len1 * self.len2) + low2 * self.len1 + low1 + let index = index / self.len2; + let low1 = index % self.len1; + let index = index / self.len1; + index * (self.len1 * self.len2) + low2 * self.len1 + low1 + } + #[inline] + fn unapply_indices(&self, indices: &[usize]) -> Vec { + indices.iter().map(|index| { + let low1 = index % self.len1; + let index = index / self.len1; + let low2 = index % self.len2; + let index = index / self.len2; + index * (self.len1 * self.len2) + low1 * self.len2 + low2 + }).collect() } #[inline] fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { @@ -144,6 +196,8 @@ impl DescribeOperator for Transpose { } } +// TODO: add StrictMonotonicIncreasingTake + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Take { indices: Box<[usize]>, @@ -160,14 +214,14 @@ impl Take { } } -impl SequenceTransformation for Take { +impl UnsizedSequence for Take { #[inline] fn delta_dim(&self) -> Dim { 0 } #[inline] - fn len(&self, parent_len: usize) -> usize { - (parent_len / self.len) * self.indices.len() + fn delta_len(&self) -> (usize, usize) { + (self.indices.len(), self.len) } #[inline] fn increment_offset(&mut self, _amount: Dim) {} @@ -181,6 +235,14 @@ impl SequenceTransformation for Take { high * self.len + self.indices[low] } #[inline] + fn unapply_indices(&self, indices: &[usize]) -> Vec { + indices.iter().filter_map(|index| { + let low = index % self.len; + let high = index / self.len; + self.indices.iter().position(|i| *i == low).map(|i| i + high * self.indices.len()) + }).collect() + } + #[inline] fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { self.apply_index(index) } @@ -210,14 +272,14 @@ impl Children { } } -impl SequenceTransformation for Children { +impl UnsizedSequence for Children { #[inline] fn delta_dim(&self) -> Dim { 0 } #[inline] - fn len(&self, parent_len: usize) -> usize { - parent_len * self.simplex.nchildren() + fn delta_len(&self) -> (usize, usize) { + (self.simplex.nchildren(), 1) } #[inline] fn increment_offset(&mut self, amount: Dim) { @@ -232,6 +294,10 @@ impl SequenceTransformation for Children { self.simplex.apply_child_index(index) } #[inline] + fn unapply_indices(&self, indices: &[usize]) -> Vec { + self.simplex.unapply_child_indices(indices) + } + #[inline] fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { self.simplex .apply_child_inplace(index, &mut coordinate[self.offset as usize..]) @@ -271,14 +337,14 @@ impl Edges { } } -impl SequenceTransformation for Edges { +impl UnsizedSequence for Edges { #[inline] fn delta_dim(&self) -> Dim { 1 } #[inline] - fn len(&self, parent_len: usize) -> usize { - parent_len * self.simplex.nedges() + fn delta_len(&self) -> (usize, usize) { + (self.simplex.nedges(), 1) } #[inline] fn increment_offset(&mut self, amount: Dim) { @@ -293,6 +359,10 @@ impl SequenceTransformation for Edges { self.simplex.apply_edge_index(index) } #[inline] + fn unapply_indices(&self, indices: &[usize]) -> Vec { + self.simplex.unapply_edge_indices(indices) + } + #[inline] fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { self.simplex .apply_edge_inplace(index, &mut coordinate[self.offset as usize..]) @@ -319,12 +389,18 @@ impl DescribeOperator for Edges { } } -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +#[derive(Debug, Clone, Copy, PartialEq)] #[repr(transparent)] struct FiniteF64(pub f64); impl Eq for FiniteF64 {} +impl PartialOrd for FiniteF64 { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + impl Ord for FiniteF64 { fn cmp(&self, other: &Self) -> Ordering { if let Some(ord) = self.0.partial_cmp(&other.0) { @@ -358,14 +434,14 @@ impl UniformPoints { } } -impl SequenceTransformation for UniformPoints { +impl UnsizedSequence for UniformPoints { #[inline] fn delta_dim(&self) -> Dim { self.point_dim } #[inline] - fn len(&self, parent_len: usize) -> usize { - parent_len * self.npoints() + fn delta_len(&self) -> (usize, usize) { + (self.npoints(), 1) } #[inline] fn increment_offset(&mut self, amount: Dim) { @@ -379,6 +455,16 @@ impl SequenceTransformation for UniformPoints { fn apply_index(&self, index: usize) -> usize { index / self.npoints() } + #[inline] + fn unapply_indices(&self, indices: &[usize]) -> Vec { + let mut point_indices = Vec::with_capacity(indices.len() * self.npoints()); + for index in indices.iter() { + for ipoint in 0..self.npoints() { + point_indices.push(index * self.npoints() + ipoint); + } + } + point_indices + } fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { let point_dim = self.point_dim as usize; let coordinate = &mut coordinate[self.offset as usize..]; @@ -412,6 +498,7 @@ impl DescribeOperator for UniformPoints { /// and the input sequence is assumed to be a multiple of the chunk size long. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum Operator { + Offset(Offset), /// The transpose of a sequence: the input sequence is reshaped to `(_, /// len1, len2)`, the last two axes are swapped and the result is /// flattened. @@ -437,9 +524,12 @@ macro_rules! impl_from_for_operator { )*} } -impl_from_for_operator! {Transpose, Take, Children, Edges, UniformPoints} +impl_from_for_operator! {Offset, Transpose, Take, Children, Edges, UniformPoints} impl Operator { + pub fn new_offset(offset: usize) -> Self { + Offset::new(offset).into() + } /// Construct a new operator that transposes a sequence of elements. pub fn new_transpose(len1: usize, len2: usize) -> Self { Transpose::new(len1, len2).into() @@ -471,6 +561,7 @@ macro_rules! dispatch { #[inline] $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { match $self { + Operator::Offset(var) => var.$fn($($arg),*), Operator::Transpose(var) => var.$fn($($arg),*), Operator::Take(var) => var.$fn($($arg),*), Operator::Children(var) => var.$fn($($arg),*), @@ -483,6 +574,7 @@ macro_rules! dispatch { #[inline] $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { match $self { + Operator::Offset(var) => var.$fn($($arg),*), Operator::Transpose(var) => var.$fn($($arg),*), Operator::Take(var) => var.$fn($($arg),*), Operator::Children(var) => var.$fn($($arg),*), @@ -493,12 +585,13 @@ macro_rules! dispatch { }; } -impl SequenceTransformation for Operator { +impl UnsizedSequence for Operator { dispatch! {fn delta_dim(&self) -> Dim} - dispatch! {fn len(&self, parent_len: usize) -> usize} + dispatch! {fn delta_len(&self) -> (usize, usize)} dispatch! {fn increment_offset(&mut self, amount: Dim)} dispatch! {fn decrement_offset(&mut self, amount: Dim)} dispatch! {fn apply_index(&self, index: usize) -> usize} + dispatch! {fn unapply_indices(&self, indices: &[usize]) -> Vec} dispatch! {fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize} dispatch! {fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: Dim) -> usize} dispatch! {fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec)} @@ -519,8 +612,8 @@ impl std::fmt::Debug for Operator { fn swap(l: &L, r: &mut R) -> Option> where - L: DescribeOperator + SequenceTransformation + Into, - R: DescribeOperator + SequenceTransformation, + L: DescribeOperator + UnsizedSequence + Into, + R: DescribeOperator + UnsizedSequence, { if let (Some(edges), Some(children)) = (l.as_edges(), r.as_children_mut()) { if edges.offset == children.offset && edges.simplex.edge_dim() == children.simplex.dim() { @@ -533,6 +626,7 @@ where } use OperatorKind::*; match (l.operator_kind(), r.operator_kind()) { + (Index(1, 1), Coordinate(_, _, _, _)) => Some(vec![l.clone().into()]), (Index(l_nout, l_nin), Coordinate(_, _, _, r_gen)) => Some(vec![ Operator::new_transpose(r_gen, l_nout), l.clone().into(), @@ -559,22 +653,31 @@ where /// A chain of [`Operator`]s. #[derive(Debug, Clone, PartialEq)] -pub struct Chain { +pub struct UnsizedChain { rev_operators: Vec, } -impl Chain { +impl UnsizedChain { #[inline] pub fn new(operators: Operators) -> Self where Operators: IntoIterator, Operators::IntoIter: DoubleEndedIterator, { - Chain { + UnsizedChain { rev_operators: operators.into_iter().rev().collect(), } } - /// Returns a clone of this [`Chain`] with the given `operator` appended. + #[inline] + pub fn empty() -> Self { + UnsizedChain { + rev_operators: Vec::new(), + } + } + pub fn push(&mut self, operator: impl Into) { + self.rev_operators.insert(0, operator.into()) + } + /// Returns a clone of this [`UnsizedChain`] with the given `operator` appended. #[inline] pub fn clone_and_push(&self, operator: Operator) -> Self { Self::new( @@ -586,7 +689,7 @@ impl Chain { ) } #[inline] - pub fn iter_operators(&self) -> impl Iterator + DoubleEndedIterator { + pub fn iter(&self) -> impl Iterator + DoubleEndedIterator { self.rev_operators.iter().rev() } fn split_heads(&self) -> BTreeMap> { @@ -644,14 +747,11 @@ impl Chain { rev_b.pop(); continue; } - let heads_a = Chain::new(rev_a.iter().rev().cloned()).split_heads(); - let heads_b = Chain::new(rev_b.iter().rev().cloned()).split_heads(); - let candidates: Vec<_> = heads_a + let heads_a = UnsizedChain::new(rev_a.iter().rev().cloned()).split_heads(); + let heads_b = UnsizedChain::new(rev_b.iter().rev().cloned()).split_heads(); + if let Some((head, a, b)) = heads_a .iter() .filter_map(|(h, a)| heads_b.get(h).map(|b| (h, a, b))) - .collect(); - if let Some((head, a, b)) = candidates - .into_iter() .min_by_key(|(_, a, b)| std::cmp::max(a.len(), b.len())) { common.push(head.clone()); @@ -678,16 +778,17 @@ impl Chain { } } -impl SequenceTransformation for Chain { +impl UnsizedSequence for UnsizedChain { #[inline] fn delta_dim(&self) -> Dim { self.rev_operators.iter().map(|op| op.delta_dim()).sum() } #[inline] - fn len(&self, parent_len: usize) -> usize { - self.rev_operators - .iter() - .rfold(parent_len, |len, op| op.len(len)) + fn delta_len(&self) -> (usize, usize) { + self.rev_operators.iter().rfold((1, 1), |(n, d), op| { + let (opn, opd) = op.delta_len(); + (n * opn, d * opd) + }) } #[inline] fn increment_offset(&mut self, amount: Dim) { @@ -708,6 +809,14 @@ impl SequenceTransformation for Chain { .fold(index, |index, op| op.apply_index(index)) } #[inline] + fn unapply_indices(&self, indices: &[usize]) -> Vec { + let indices = indices.iter().cloned().collect(); + self.rev_operators + .iter() + .rev() + .fold(indices, |indices, op| op.unapply_indices(&indices)) + } + #[inline] fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { self.rev_operators .iter() @@ -721,6 +830,25 @@ impl SequenceTransformation for Chain { } } +impl IntoIterator for UnsizedChain { + type Item = Operator; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.rev_operators.into_iter() + } +} + +impl FromIterator for UnsizedChain { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + let ops: Vec<_> = iter.into_iter().collect(); + UnsizedChain::new(ops) + } +} + //#[derive(Debug, Clone)] //struct AbsoluteChains { // tip_dim: Dim, @@ -809,7 +937,7 @@ impl SequenceTransformation for Chain { // //impl RelativeChain { // pub fn get_chain(&self, index: usize) -> Option<(Chain, usize, usize)> { -// for (offset, indices, +// for (offset, indices, // pub fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { // self.get_chain(index).map(|(chain, offset, index)| { // offset + chain.apply_many_inplace(index, coordinates, self.tip_dim()) @@ -824,7 +952,7 @@ impl SequenceTransformation for Chain { #[derive(Debug, Clone)] pub struct Topology { - transforms: Chain, + transforms: UnsizedChain, dim: Dim, root_len: usize, len: usize, @@ -833,16 +961,17 @@ pub struct Topology { impl Topology { pub fn new(dim: Dim, len: usize) -> Self { Self { - transforms: Chain::new([]), + transforms: UnsizedChain::new([]), dim, root_len: len, len, } } pub fn derive(&self, operator: Operator) -> Self { + let (n, d) = operator.delta_len(); Self { root_len: self.root_len, - len: operator.len(self.len), + len: self.len * n / d, dim: self.dim - operator.delta_dim(), transforms: self.transforms.clone_and_push(operator), } @@ -854,14 +983,14 @@ impl Mul for &Topology { fn mul(self, other: &Topology) -> Topology { Topology { - transforms: Chain::new( + transforms: UnsizedChain::new( iter::once(Operator::new_transpose(other.root_len, self.root_len)) - .chain(self.transforms.iter_operators().cloned()) + .chain(self.transforms.iter().cloned()) .chain(iter::once(Operator::new_transpose( self.len, other.root_len, ))) - .chain(other.transforms.iter_operators().map(|op| { + .chain(other.transforms.iter().map(|op| { let mut op = op.clone(); op.increment_offset(self.dim); op @@ -880,6 +1009,44 @@ mod tests { use approx::assert_abs_diff_eq; use Simplex::*; + #[test] + fn unapply_indices_offset() { + let offset = Offset::new(4); + assert_eq!(offset.unapply_indices(&[3, 4, 6]), vec![0, 2]); + assert_eq!(offset.unapply_indices(&[3]), vec![]); + } + + #[test] + fn unapply_indices_transpose() { + let transpose = Transpose::new(2, 3); + assert_eq!(transpose.unapply_indices(&[0, 1, 3, 8, 13, 14]), vec![0, 3, 4, 7, 15, 13]); + } + + #[test] + fn unapply_indices_take() { + let take = Take::new([4, 1], 5); + assert_eq!(take.unapply_indices(&[0, 1, 4, 5, 8, 9]), vec![1, 0, 2]); + assert_eq!(take.unapply_indices(&[0]), vec![]); + } + + #[test] + fn unapply_indices_children() { + let children = Children::new(Triangle, 0); + assert_eq!(children.unapply_indices(&[0, 2]), vec![0, 1, 2, 3, 8, 9, 10, 11]); + } + + #[test] + fn unapply_indices_edges() { + let edges = Edges::new(Triangle, 0); + assert_eq!(edges.unapply_indices(&[0, 2]), vec![0, 1, 2, 6, 7, 8]); + } + + #[test] + fn unapply_indices_uniform_points() { + let points = UniformPoints::new(Box::new([0.0, 0.0, 1.0, 0.0, 0.0, 1.0]), 2, 0); + assert_eq!(points.unapply_indices(&[0, 2]), vec![0, 1, 2, 6, 7, 8]); + } + macro_rules! assert_eq_op_apply { ($op:expr, $ii:expr, $ic:expr, $oi:expr, $oc:expr) => {{ let ic = $ic; @@ -1180,7 +1347,7 @@ mod tests { #[test] fn split_heads() { - let chain = Chain::new([ + let chain = UnsizedChain::new([ Operator::new_edges(Triangle, 1), Operator::new_children(Line, 0), Operator::new_edges(Line, 2), @@ -1188,7 +1355,7 @@ mod tests { Operator::new_children(Line, 0), ]); let desired = chain - .iter_operators() + .iter() .cloned() .fold(Topology::new(4, 1), |topo, op| topo.derive(op)); for (head, tail) in chain.split_heads().into_iter() { @@ -1201,20 +1368,20 @@ mod tests { #[test] fn remove_common_prefix() { - let a = Chain::new([ + let a = UnsizedChain::new([ Operator::new_children(Line, 0), Operator::new_children(Line, 0), ]); - let b = Chain::new([Operator::new_edges(Line, 0)]); + let b = UnsizedChain::new([Operator::new_edges(Line, 0)]); assert_eq!( a.remove_common_prefix(&b), ( - Chain::new([ + UnsizedChain::new([ Operator::new_children(Line, 0), Operator::new_children(Line, 0) ]), - Chain::new([]), - Chain::new([ + UnsizedChain::new([]), + UnsizedChain::new([ Operator::new_edges(Line, 0), Operator::new_take([2, 1], 4), Operator::new_take([2, 1], 4) diff --git a/src/lib.rs b/src/lib.rs index 869ca3ce3..c14ef2adc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod simplex; pub mod types; pub mod chain; +pub mod sequence; diff --git a/src/sequence.rs b/src/sequence.rs new file mode 100644 index 000000000..ffcdef64f --- /dev/null +++ b/src/sequence.rs @@ -0,0 +1,126 @@ +use crate::chain::{Operator, UnsizedChain, UnsizedSequence as _}; +use crate::types::Dim; +use std::iter; +use std::ops::Mul; + +pub trait Sequence { + fn len(&self) -> usize; + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn root_len(&self) -> usize; + fn dim(&self) -> Dim; + fn root_dim(&self) -> Dim; + fn apply_index(&self, index: usize) -> Option; + fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option; + fn apply(&self, index: usize, coordinates: &[f64]) -> Option<(usize, Vec)>; +} + +#[derive(Debug, Clone)] +pub struct Chain { + root_dim: Dim, + root_len: usize, + chain: UnsizedChain, +} + +impl Chain { + pub fn new(root_dim: Dim, root_len: usize, chain: impl Into) -> Self { + Self { + root_dim, + root_len, + chain: chain.into(), + } + } + pub fn empty(dim: Dim, len: usize) -> Self { + Self { + root_dim: dim, + root_len: len, + chain: UnsizedChain::empty(), + } + } + pub fn push(&mut self, operator: impl Into) { + self.chain.push(operator.into()); + } +} + +impl Sequence for Chain { + fn len(&self) -> usize { + let (n, d) = self.chain.delta_len(); + self.root_len * n / d + } + fn root_len(&self) -> usize { + self.root_len + } + fn dim(&self) -> Dim { + self.root_dim - self.chain.delta_dim() + } + fn root_dim(&self) -> Dim { + self.root_dim + } + fn apply_index(&self, index: usize) -> Option { + if index < self.len() { + let root_index = self.chain.apply_index(index); + assert!(root_index < self.root_len); + Some(root_index) + } else { + None + } + } + fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { + if index < self.len() { + let root_index = self + .chain + .apply_many_inplace(index, coordinates, self.root_dim); + assert!(root_index < self.root_len); + Some(root_index) + } else { + None + } + } + fn apply(&self, index: usize, coordinates: &[f64]) -> Option<(usize, Vec)> { + if index < self.len() { + let (root_index, root_coords) = self.chain.apply_many(index, coordinates, self.dim()); + assert!(root_index < self.root_len); + Some((root_index, root_coords)) + } else { + None + } + } +} + +impl Mul for Chain { + type Output = Self; + + fn mul(self, mut other: Self) -> Self { + let root_dim = self.root_dim() + other.root_dim(); + let root_len = self.root_len() * other.root_len(); + let trans1 = Operator::new_transpose(other.root_len(), self.root_len()); + let trans2 = Operator::new_transpose(self.len(), other.root_len()); + other.chain.increment_offset(self.dim()); + let chain: UnsizedChain = iter::once(trans1) + .chain(self.chain.into_iter()) + .chain(iter::once(trans2)) + .chain(other.chain.into_iter()) + .collect(); + Chain::new(root_dim, root_len, chain) + } +} + +fn get_root_indices(seq: impl Sequence) -> Vec { + (0..seq.len()).into_iter().map(|i| seq.apply_index(i).unwrap()).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::chain::{Children, Edges}; + use crate::simplex::Simplex::*; + + #[test] + fn test() { + let mut chain = Chain::empty(1, 2); + chain.push(Children::new(Line, 0)); + assert_eq!(get_root_indices(chain), vec![0, 0, 1, 1]); + // Find the root indices of the other tail. + } +} diff --git a/src/simplex.rs b/src/simplex.rs index 00a782901..53f4cafab 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -123,6 +123,17 @@ impl Simplex { Self::Triangle => index / 4, } } + + pub fn unapply_child_indices(&self, indices: &[usize]) -> Vec { + let mut child_indices = Vec::with_capacity(indices.len() * self.nchildren()); + for index in indices.iter() { + for ichild in 0..self.nchildren() { + child_indices.push(index * self.nchildren() + ichild); + } + } + child_indices + } + /// Transform the given edge `coordinate` for edge `index` to this parent /// simplex in-place. The returned index is the index of the parent in an /// infinite, uniform sequence. @@ -160,6 +171,16 @@ impl Simplex { Self::Triangle => index / 3, } } + + pub fn unapply_edge_indices(&self, indices: &[usize]) -> Vec { + let mut edge_indices = Vec::with_capacity(indices.len() * self.nedges()); + for index in indices.iter() { + for iedge in 0..self.nedges() { + edge_indices.push(index * self.nedges() + iedge); + } + } + edge_indices + } } #[cfg(test)] From 1cdcbf3284b9a529688f3cf79b5aa4cd542ebeda Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Thu, 16 Jun 2022 11:40:57 +0200 Subject: [PATCH 09/45] WIP --- Cargo.toml | 1 + src/chain.rs | 2632 ++++++++++++++++++++++----------------------- src/finite_f64.rs | 23 + src/lib.rs | 7 +- src/operator.rs | 664 ++++++++++++ src/simplex.rs | 181 ++-- src/types.rs | 2 - 7 files changed, 2066 insertions(+), 1444 deletions(-) create mode 100644 src/finite_f64.rs create mode 100644 src/operator.rs delete mode 100644 src/types.rs diff --git a/Cargo.toml b/Cargo.toml index ad6e5f58c..b50a80617 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] approx = "0.5" +num = "0.4" diff --git a/src/chain.rs b/src/chain.rs index a37385761..7319e109f 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,1392 +1,1308 @@ -use crate::simplex::Simplex; -use crate::types::Dim; -use std::cmp::Ordering; -use std::collections::BTreeMap; -use std::iter; -use std::ops::Mul; - -pub(crate) trait UnsizedSequence: Clone { - /// Returns the difference between the dimension of the input and the output sequence. - fn delta_dim(&self) -> Dim; - fn delta_len(&self) -> (usize, usize); - fn increment_offset(&mut self, amount: Dim); - /// Increment the offset of the coordinate transformation, if applicable. - fn decrement_offset(&mut self, amount: Dim); - /// Map the index. - fn apply_index(&self, index: usize) -> usize; - /// Returns all indices that map to the given indices. TODO: examples with take and children? - fn unapply_indices(&self, indices: &[usize]) -> Vec; - /// Map the index and coordinate of an element in the output sequence to - /// the input sequence. The index is returned, the coordinate is adjusted - /// in-place. If the coordinate dimension of the input sequence is larger - /// than that of the output sequence, the [Operator::delta_dim()] last - /// elements of the coordinate are discarded. - fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize; - /// Map the index and multiple coordinates of an element in the output - /// sequence to the input sequence. The index is returned, the coordinates - /// are adjusted in-place. - fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: Dim) -> usize { - let dim = dim as usize; - let mut result_index = 0; - for i in 0..coordinates.len() / dim { - result_index = self.apply_one_inplace(index, &mut coordinates[i * dim..(i + 1) * dim]); - } - result_index - } - /// Map the index and coordinate of an element in the output sequence to - /// the input sequence. - fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec) { - let delta_dim = self.delta_dim() as usize; - let to_dim = coordinate.len() + delta_dim; - let mut result = Vec::with_capacity(to_dim); - result.extend_from_slice(coordinate); - result.extend(iter::repeat(0.0).take(delta_dim)); - (self.apply_one_inplace(index, &mut result), result) - } - /// Map the index and multiple coordinates of an element in the output - /// sequence to the input sequence. - fn apply_many(&self, index: usize, coordinates: &[f64], dim: Dim) -> (usize, Vec) { - assert_eq!(coordinates.len() % dim as usize, 0); - let ncoords = coordinates.len() / dim as usize; - let delta_dim = self.delta_dim(); - let to_dim = dim + delta_dim; - let mut result = Vec::with_capacity(ncoords * to_dim as usize); - for coord in coordinates.chunks(dim as usize) { - result.extend_from_slice(coord); - result.extend(iter::repeat(0.0).take(delta_dim as usize)); - } - (self.apply_many_inplace(index, &mut result, to_dim), result) - } -} - -#[derive(Debug, Clone, PartialEq)] -enum OperatorKind { - Index(usize, usize), - Coordinate(Dim, Dim, Dim, usize), - Other, -} - -trait DescribeOperator { - /// Returns the kind of operation the operator applies to its parent sequence. - fn operator_kind(&self) -> OperatorKind; - #[inline] - fn as_children(&self) -> Option<&Children> { - None - } - #[inline] - fn as_children_mut(&mut self) -> Option<&mut Children> { - None - } - #[inline] - fn as_edges(&self) -> Option<&Edges> { - None - } - #[inline] - fn as_edges_mut(&mut self) -> Option<&mut Edges> { - None - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Offset(usize); - -impl Offset { - #[inline] - pub fn new(offset: usize) -> Self { - Self(offset) - } -} - -impl UnsizedSequence for Offset { - #[inline] - fn delta_dim(&self) -> Dim { - 0 - } - #[inline] - fn delta_len(&self) -> (usize, usize) { - (1, 1) - } - #[inline] - fn increment_offset(&mut self, _amount: Dim) {} - #[inline] - fn decrement_offset(&mut self, _amount: Dim) {} - #[inline] - fn apply_index(&self, index: usize) -> usize { - index + self.0 - } - #[inline] - fn unapply_indices(&self, indices: &[usize]) -> Vec { - indices.iter().filter_map(|i| i.checked_sub(self.0)).collect() - } - #[inline] - fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { - self.apply_index(index) - } - #[inline] - fn apply_many_inplace(&self, index: usize, _coordinate: &mut [f64], _dim: Dim) -> usize { - self.apply_index(index) - } -} - -impl DescribeOperator for Offset { - #[inline] - fn operator_kind(&self) -> OperatorKind { - OperatorKind::Other - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Transpose { - len1: usize, - len2: usize, -} - -impl Transpose { - #[inline] - pub fn new(len1: usize, len2: usize) -> Self { - Self { len1, len2 } - } -} - -impl UnsizedSequence for Transpose { - #[inline] - fn delta_dim(&self) -> Dim { - 0 - } - #[inline] - fn delta_len(&self) -> (usize, usize) { - (1, 1) - } - #[inline] - fn increment_offset(&mut self, _amount: Dim) {} - #[inline] - fn decrement_offset(&mut self, _amount: Dim) {} - #[inline] - fn apply_index(&self, index: usize) -> usize { - let low2 = index % self.len2; - let index = index / self.len2; - let low1 = index % self.len1; - let index = index / self.len1; - index * (self.len1 * self.len2) + low2 * self.len1 + low1 - } - #[inline] - fn unapply_indices(&self, indices: &[usize]) -> Vec { - indices.iter().map(|index| { - let low1 = index % self.len1; - let index = index / self.len1; - let low2 = index % self.len2; - let index = index / self.len2; - index * (self.len1 * self.len2) + low1 * self.len2 + low2 - }).collect() - } - #[inline] - fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { - self.apply_index(index) - } - #[inline] - fn apply_many_inplace(&self, index: usize, _coordinate: &mut [f64], _dim: Dim) -> usize { - self.apply_index(index) - } -} - -impl DescribeOperator for Transpose { - #[inline] - fn operator_kind(&self) -> OperatorKind { - OperatorKind::Index(self.len1 * self.len2, self.len1 * self.len2) - } -} - -// TODO: add StrictMonotonicIncreasingTake - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Take { - indices: Box<[usize]>, - len: usize, -} - -impl Take { - #[inline] - pub fn new(indices: impl Into>, len: usize) -> Self { - Self { - indices: indices.into(), - len, - } - } -} - -impl UnsizedSequence for Take { - #[inline] - fn delta_dim(&self) -> Dim { - 0 - } - #[inline] - fn delta_len(&self) -> (usize, usize) { - (self.indices.len(), self.len) - } - #[inline] - fn increment_offset(&mut self, _amount: Dim) {} - #[inline] - fn decrement_offset(&mut self, _amount: Dim) {} - #[inline] - fn apply_index(&self, index: usize) -> usize { - let nindices = self.indices.len(); - let low = index % nindices; - let high = index / nindices; - high * self.len + self.indices[low] - } - #[inline] - fn unapply_indices(&self, indices: &[usize]) -> Vec { - indices.iter().filter_map(|index| { - let low = index % self.len; - let high = index / self.len; - self.indices.iter().position(|i| *i == low).map(|i| i + high * self.indices.len()) - }).collect() - } - #[inline] - fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { - self.apply_index(index) - } - #[inline] - fn apply_many_inplace(&self, index: usize, _coordinate: &mut [f64], _dim: Dim) -> usize { - self.apply_index(index) - } -} - -impl DescribeOperator for Take { - #[inline] - fn operator_kind(&self) -> OperatorKind { - OperatorKind::Index(self.len, self.indices.len()) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Children { - simplex: Simplex, - offset: Dim, -} - -impl Children { - #[inline] - pub fn new(simplex: Simplex, offset: Dim) -> Self { - Self { simplex, offset } - } -} - -impl UnsizedSequence for Children { - #[inline] - fn delta_dim(&self) -> Dim { - 0 - } - #[inline] - fn delta_len(&self) -> (usize, usize) { - (self.simplex.nchildren(), 1) - } - #[inline] - fn increment_offset(&mut self, amount: Dim) { - self.offset += amount; - } - #[inline] - fn decrement_offset(&mut self, amount: Dim) { - self.offset -= amount; - } - #[inline] - fn apply_index(&self, index: usize) -> usize { - self.simplex.apply_child_index(index) - } - #[inline] - fn unapply_indices(&self, indices: &[usize]) -> Vec { - self.simplex.unapply_child_indices(indices) - } - #[inline] - fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { - self.simplex - .apply_child_inplace(index, &mut coordinate[self.offset as usize..]) - } -} - -impl DescribeOperator for Children { - #[inline] - fn operator_kind(&self) -> OperatorKind { - OperatorKind::Coordinate( - self.offset, - self.simplex.dim(), - self.simplex.dim(), - self.simplex.nchildren(), - ) - } - #[inline] - fn as_children(&self) -> Option<&Children> { - Some(self) - } - #[inline] - fn as_children_mut(&mut self) -> Option<&mut Children> { - Some(self) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Edges { - simplex: Simplex, - offset: Dim, -} - -impl Edges { - #[inline] - pub fn new(simplex: Simplex, offset: Dim) -> Self { - Self { simplex, offset } - } -} - -impl UnsizedSequence for Edges { - #[inline] - fn delta_dim(&self) -> Dim { - 1 - } - #[inline] - fn delta_len(&self) -> (usize, usize) { - (self.simplex.nedges(), 1) - } - #[inline] - fn increment_offset(&mut self, amount: Dim) { - self.offset += amount; - } - #[inline] - fn decrement_offset(&mut self, amount: Dim) { - self.offset -= amount; - } - #[inline] - fn apply_index(&self, index: usize) -> usize { - self.simplex.apply_edge_index(index) - } - #[inline] - fn unapply_indices(&self, indices: &[usize]) -> Vec { - self.simplex.unapply_edge_indices(indices) - } - #[inline] - fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { - self.simplex - .apply_edge_inplace(index, &mut coordinate[self.offset as usize..]) - } -} - -impl DescribeOperator for Edges { - #[inline] - fn operator_kind(&self) -> OperatorKind { - OperatorKind::Coordinate( - self.offset, - self.simplex.dim(), - self.simplex.edge_dim(), - self.simplex.nedges(), - ) - } - #[inline] - fn as_edges(&self) -> Option<&Edges> { - Some(self) - } - #[inline] - fn as_edges_mut(&mut self) -> Option<&mut Edges> { - Some(self) - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(transparent)] -struct FiniteF64(pub f64); - -impl Eq for FiniteF64 {} - -impl PartialOrd for FiniteF64 { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for FiniteF64 { - fn cmp(&self, other: &Self) -> Ordering { - if let Some(ord) = self.0.partial_cmp(&other.0) { - ord - } else { - panic!("not finite"); - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct UniformPoints { - points: Box<[FiniteF64]>, - point_dim: Dim, - offset: Dim, -} - -impl UniformPoints { - pub fn new(points: Box<[f64]>, point_dim: Dim, offset: Dim) -> Self { - // TODO: assert that the points are actually finite. - let points: Box<[FiniteF64]> = unsafe { std::mem::transmute(points) }; - Self { - points, - point_dim, - offset, - } - } - #[inline] - pub const fn npoints(&self) -> usize { - self.points.len() / self.point_dim as usize - } -} - -impl UnsizedSequence for UniformPoints { - #[inline] - fn delta_dim(&self) -> Dim { - self.point_dim - } - #[inline] - fn delta_len(&self) -> (usize, usize) { - (self.npoints(), 1) - } - #[inline] - fn increment_offset(&mut self, amount: Dim) { - self.offset += amount; - } - #[inline] - fn decrement_offset(&mut self, amount: Dim) { - self.offset -= amount; - } - #[inline] - fn apply_index(&self, index: usize) -> usize { - index / self.npoints() - } - #[inline] - fn unapply_indices(&self, indices: &[usize]) -> Vec { - let mut point_indices = Vec::with_capacity(indices.len() * self.npoints()); - for index in indices.iter() { - for ipoint in 0..self.npoints() { - point_indices.push(index * self.npoints() + ipoint); - } - } - point_indices - } - fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { - let point_dim = self.point_dim as usize; - let coordinate = &mut coordinate[self.offset as usize..]; - coordinate.copy_within(..coordinate.len() - point_dim, point_dim); - let ipoint = index % self.npoints(); - let offset = ipoint as usize * point_dim; - let points: &[f64] = - unsafe { std::mem::transmute(&self.points[offset..offset + point_dim]) }; - coordinate[..point_dim].copy_from_slice(points); - index / self.npoints() - } -} - -impl DescribeOperator for UniformPoints { - #[inline] - fn operator_kind(&self) -> OperatorKind { - OperatorKind::Coordinate(self.offset, self.point_dim, 0, self.npoints()) - } -} - -/// An operator that maps a sequence of elements to another sequence of elements. -/// -/// Given a sequence of elements an [`Operator`] defines a new sequence. For -/// example [`Operator::Children`] gives the sequence of child elements and -/// [`Operator::Take`] gives a subset of the input sequence. -/// -/// All variants of [`Operator`] apply some operation to either every element of -/// the parent sequence, variants [`Operator::Children`], [`Operator::Edges`] -/// and [`Operator::UniformPoints`], or to consecutive chunks of the input -/// sequence, in which case the size of the chunks is included in the variant -/// and the input sequence is assumed to be a multiple of the chunk size long. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Operator { - Offset(Offset), - /// The transpose of a sequence: the input sequence is reshaped to `(_, - /// len1, len2)`, the last two axes are swapped and the result is - /// flattened. - Transpose(Transpose), - /// A subset of a sequence: the input sequence is reshaped to `(_, len)`, - /// the given `indices` are taken from the last axis and the result is - /// flattened. - Take(Take), - /// The children of a every element of a sequence. - Children(Children), - /// The edges of a every element of a sequence. - Edges(Edges), - UniformPoints(UniformPoints), -} - -macro_rules! impl_from_for_operator { - ($($Variant:ident),*) => {$( - impl From<$Variant> for Operator { - fn from(variant: $Variant) -> Self { - Self::$Variant(variant) - } - } - )*} -} - -impl_from_for_operator! {Offset, Transpose, Take, Children, Edges, UniformPoints} - -impl Operator { - pub fn new_offset(offset: usize) -> Self { - Offset::new(offset).into() - } - /// Construct a new operator that transposes a sequence of elements. - pub fn new_transpose(len1: usize, len2: usize) -> Self { - Transpose::new(len1, len2).into() - } - /// Construct a new operator that takes a subset of a sequence of elements. - pub fn new_take(indices: impl Into>, len: usize) -> Self { - Take::new(indices, len).into() - } - /// Construct a new operator that maps a sequence of elements to its children. - pub fn new_children(simplex: Simplex, offset: Dim) -> Self { - Children::new(simplex, offset).into() - } - /// Construct a new operator that maps a sequence of elements to its edges. - pub fn new_edges(simplex: Simplex, offset: Dim) -> Self { - Edges::new(simplex, offset).into() - } - /// Construct a new operator that adds points to every element of a sequence. - pub fn new_uniform_points(points: Box<[f64]>, point_dim: Dim, offset: Dim) -> Self { - UniformPoints::new(points, point_dim, offset).into() - } - pub fn swap(&self, other: &Self) -> Option> { - let mut other = other.clone(); - swap(self, &mut other).map(|tail| iter::once(other).chain(tail.into_iter()).collect()) - } -} - -macro_rules! dispatch { - ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { - #[inline] - $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { - match $self { - Operator::Offset(var) => var.$fn($($arg),*), - Operator::Transpose(var) => var.$fn($($arg),*), - Operator::Take(var) => var.$fn($($arg),*), - Operator::Children(var) => var.$fn($($arg),*), - Operator::Edges(var) => var.$fn($($arg),*), - Operator::UniformPoints(var) => var.$fn($($arg),*), - } - } - }; - ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { - #[inline] - $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { - match $self { - Operator::Offset(var) => var.$fn($($arg),*), - Operator::Transpose(var) => var.$fn($($arg),*), - Operator::Take(var) => var.$fn($($arg),*), - Operator::Children(var) => var.$fn($($arg),*), - Operator::Edges(var) => var.$fn($($arg),*), - Operator::UniformPoints(var) => var.$fn($($arg),*), - } - } - }; -} - -impl UnsizedSequence for Operator { - dispatch! {fn delta_dim(&self) -> Dim} - dispatch! {fn delta_len(&self) -> (usize, usize)} - dispatch! {fn increment_offset(&mut self, amount: Dim)} - dispatch! {fn decrement_offset(&mut self, amount: Dim)} - dispatch! {fn apply_index(&self, index: usize) -> usize} - dispatch! {fn unapply_indices(&self, indices: &[usize]) -> Vec} - dispatch! {fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize} - dispatch! {fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: Dim) -> usize} - dispatch! {fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec)} - dispatch! {fn apply_many(&self, index: usize, coordinates: &[f64], dim: Dim) -> (usize, Vec)} -} - -impl DescribeOperator for Operator { - dispatch! {fn operator_kind(&self) -> OperatorKind} - dispatch! {fn as_children(&self) -> Option<&Children>} - dispatch! {fn as_children_mut(&mut self) -> Option<&mut Children>} - dispatch! {fn as_edges(&self) -> Option<&Edges>} - dispatch! {fn as_edges_mut(&mut self) -> Option<&mut Edges>} -} - -impl std::fmt::Debug for Operator { - dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result} -} - -fn swap(l: &L, r: &mut R) -> Option> -where - L: DescribeOperator + UnsizedSequence + Into, - R: DescribeOperator + UnsizedSequence, -{ - if let (Some(edges), Some(children)) = (l.as_edges(), r.as_children_mut()) { - if edges.offset == children.offset && edges.simplex.edge_dim() == children.simplex.dim() { - let simplex = edges.simplex; - let indices = simplex.swap_edges_children_map(); - let take = Operator::new_take(indices, simplex.nchildren() * simplex.nedges()); - children.simplex = simplex; - return Some(vec![l.clone().into(), take]); - } - } - use OperatorKind::*; - match (l.operator_kind(), r.operator_kind()) { - (Index(1, 1), Coordinate(_, _, _, _)) => Some(vec![l.clone().into()]), - (Index(l_nout, l_nin), Coordinate(_, _, _, r_gen)) => Some(vec![ - Operator::new_transpose(r_gen, l_nout), - l.clone().into(), - Operator::new_transpose(l_nin, r_gen), - ]), - (Coordinate(l_off, _, l_nin, l_gen), Coordinate(r_off, r_nout, _, r_gen)) => { - if l_off + l_nin <= r_off { - r.increment_offset(l.delta_dim()); - Some(vec![ - l.clone().into(), - Operator::new_transpose(l_gen, r_gen), - ]) - } else if l_off >= r_off + r_nout { - let mut l = l.clone(); - l.decrement_offset(r.delta_dim()); - Some(vec![l.into(), Operator::new_transpose(l_gen, r_gen)]) - } else { - None - } - } - _ => None, - } -} - -/// A chain of [`Operator`]s. -#[derive(Debug, Clone, PartialEq)] -pub struct UnsizedChain { - rev_operators: Vec, -} - -impl UnsizedChain { - #[inline] - pub fn new(operators: Operators) -> Self - where - Operators: IntoIterator, - Operators::IntoIter: DoubleEndedIterator, - { - UnsizedChain { - rev_operators: operators.into_iter().rev().collect(), - } - } - #[inline] - pub fn empty() -> Self { - UnsizedChain { - rev_operators: Vec::new(), - } - } - pub fn push(&mut self, operator: impl Into) { - self.rev_operators.insert(0, operator.into()) - } - /// Returns a clone of this [`UnsizedChain`] with the given `operator` appended. - #[inline] - pub fn clone_and_push(&self, operator: Operator) -> Self { - Self::new( - self.rev_operators - .iter() - .rev() - .cloned() - .chain(iter::once(operator)), - ) - } - #[inline] - pub fn iter(&self) -> impl Iterator + DoubleEndedIterator { - self.rev_operators.iter().rev() - } - fn split_heads(&self) -> BTreeMap> { - let mut heads = BTreeMap::new(); - 'a: for (i, head) in self.rev_operators.iter().enumerate() { - let mut rev_tail: Vec<_> = self.rev_operators.iter().take(i).cloned().collect(); - let mut head = head.clone(); - for op in self.rev_operators.iter().skip(i + 1) { - if let Some(ops) = swap(op, &mut head) { - rev_tail.extend(ops.into_iter().rev()); - } else { - continue 'a; - } - } - heads.insert(head, rev_tail); - } - 'b: for (i, op) in self.rev_operators.iter().enumerate() { - if let Operator::Edges(Edges { - simplex: Simplex::Line, - offset, - }) = op - { - let simplex = Simplex::Line; - let mut rev_tail: Vec<_> = self.rev_operators.iter().take(i).cloned().collect(); - let mut head = Operator::new_children(simplex, *offset); - let indices = simplex.swap_edges_children_map(); - let take = Operator::new_take(indices, simplex.nchildren() * simplex.nedges()); - rev_tail.push(take); - rev_tail.push(op.clone()); - for op in self.rev_operators.iter().skip(i + 1) { - if let Some(ops) = swap(op, &mut head) { - rev_tail.extend(ops.into_iter().rev()); - } else { - continue 'b; - } - } - heads.insert(head, rev_tail); - } - } - heads - } - /// Remove and return the common prefix of two chains, transforming either if necessary. - pub fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { - let mut common = Vec::new(); - let mut rev_a = self.rev_operators.clone(); - let mut rev_b = other.rev_operators.clone(); - let mut i = 0; - while !rev_a.is_empty() && !rev_b.is_empty() { - i += 1; - if i > 10 { - break; - } - if rev_a.last() == rev_b.last() { - common.push(rev_a.pop().unwrap()); - rev_b.pop(); - continue; - } - let heads_a = UnsizedChain::new(rev_a.iter().rev().cloned()).split_heads(); - let heads_b = UnsizedChain::new(rev_b.iter().rev().cloned()).split_heads(); - if let Some((head, a, b)) = heads_a - .iter() - .filter_map(|(h, a)| heads_b.get(h).map(|b| (h, a, b))) - .min_by_key(|(_, a, b)| std::cmp::max(a.len(), b.len())) - { - common.push(head.clone()); - rev_a = a.clone(); - rev_b = b.clone(); - continue; - } - break; - } - let common = if rev_a.is_empty() - && (!rev_b.is_empty() || self.rev_operators.len() <= other.rev_operators.len()) - { - self.clone() - } else if rev_b.is_empty() { - other.clone() - } else { - Self::new(common) - }; - ( - common, - Self::new(rev_a.into_iter().rev()), - Self::new(rev_b.into_iter().rev()), - ) - } -} - -impl UnsizedSequence for UnsizedChain { - #[inline] - fn delta_dim(&self) -> Dim { - self.rev_operators.iter().map(|op| op.delta_dim()).sum() - } - #[inline] - fn delta_len(&self) -> (usize, usize) { - self.rev_operators.iter().rfold((1, 1), |(n, d), op| { - let (opn, opd) = op.delta_len(); - (n * opn, d * opd) - }) - } - #[inline] - fn increment_offset(&mut self, amount: Dim) { - for op in self.rev_operators.iter_mut() { - op.increment_offset(amount); - } - } - #[inline] - fn decrement_offset(&mut self, amount: Dim) { - for op in self.rev_operators.iter_mut() { - op.decrement_offset(amount); - } - } - #[inline] - fn apply_index(&self, index: usize) -> usize { - self.rev_operators - .iter() - .fold(index, |index, op| op.apply_index(index)) - } - #[inline] - fn unapply_indices(&self, indices: &[usize]) -> Vec { - let indices = indices.iter().cloned().collect(); - self.rev_operators - .iter() - .rev() - .fold(indices, |indices, op| op.unapply_indices(&indices)) - } - #[inline] - fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { - self.rev_operators - .iter() - .fold(index, |index, op| op.apply_one_inplace(index, coordinate)) - } - #[inline] - fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: Dim) -> usize { - self.rev_operators.iter().fold(index, |index, op| { - op.apply_many_inplace(index, coordinates, dim) - }) - } -} - -impl IntoIterator for UnsizedChain { - type Item = Operator; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.rev_operators.into_iter() - } -} - -impl FromIterator for UnsizedChain { - fn from_iter(iter: T) -> Self - where - T: IntoIterator, - { - let ops: Vec<_> = iter.into_iter().collect(); - UnsizedChain::new(ops) - } -} - -//#[derive(Debug, Clone)] -//struct AbsoluteChains { -// tip_dim: Dim, -// delta_dim: Dim, -// chains: Vec<(usize, Chain)>, +//pub(crate) trait UnsizedSequence: Clone { +// /// Returns the difference between the dimension of the input and the output sequence. +// fn delta_dim(&self) -> usize; +// fn delta_len(&self) -> (usize, usize); +// /// Map the index. +// fn apply_index(&self, index: usize) -> usize; +// /// Returns all indices that map to the given indices. TODO: examples with take and children? +// fn unapply_indices(&self, indices: &[usize]) -> Vec; +// /// Map the index and coordinate of an element in the output sequence to +// /// the input sequence. The index is returned, the coordinate is adjusted +// /// in-place. If the coordinate dimension of the input sequence is larger +// /// than that of the output sequence, the [Operator::delta_dim()] last +// /// elements of the coordinate are discarded. +// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize; +// /// Map the index and multiple coordinates of an element in the output +// /// sequence to the input sequence. The index is returned, the coordinates +// /// are adjusted in-place. +// fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize, stride: usize) -> usize { +// let dim = dim as usize; +// let mut result_index = 0; +// for i in 0..coordinates.len() / stride { +// result_index = self.apply_one_inplace(index, &mut coordinates[i * dim..(i + 1) * dim]); +// } +// result_index +// } +// /// Map the index and coordinate of an element in the output sequence to +// /// the input sequence. +// fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec) { +// let delta_dim = self.delta_dim() as usize; +// let to_dim = coordinate.len() + delta_dim; +// let mut result = Vec::with_capacity(to_dim); +// result.extend_from_slice(coordinate); +// result.extend(iter::repeat(0.0).take(delta_dim)); +// (self.apply_one_inplace(index, &mut result), result) +// } +// /// Map the index and multiple coordinates of an element in the output +// /// sequence to the input sequence. +// fn apply_many(&self, index: usize, coordinates: &[f64], dim: usize) -> (usize, Vec) { +// assert_eq!(coordinates.len() % dim as usize, 0); +// let ncoords = coordinates.len() / dim as usize; +// let delta_dim = self.delta_dim(); +// let to_dim = dim + delta_dim; +// let mut result = Vec::with_capacity(ncoords * to_dim as usize); +// for coord in coordinates.chunks(dim as usize) { +// result.extend_from_slice(coord); +// result.extend(iter::repeat(0.0).take(delta_dim as usize)); +// } +// (self.apply_many_inplace(index, &mut result, to_dim), result) +// } +//} +// +//macro_rules! dispatch { +// ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { +// #[inline] +// $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { +// match $self { +// Operator::Take(var) => var.$fn($($arg),*), +// Operator::Children(var) => var.$fn($($arg),*), +// Operator::Edges(var) => var.$fn($($arg),*), +// Operator::UniformPoints(var) => var.$fn($($arg),*), +// } +// } +// }; +// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { +// #[inline] +// $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { +// match $self { +// Operator::Take(var) => var.$fn($($arg),*), +// Operator::Children(var) => var.$fn($($arg),*), +// Operator::Edges(var) => var.$fn($($arg),*), +// Operator::UniformPoints(var) => var.$fn($($arg),*), +// } +// } +// }; +//} +// +//impl UnsizedSequence for Operator { +// dispatch! {fn delta_dim(&self) -> usize} +// dispatch! {fn delta_len(&self) -> (usize, usize)} +// dispatch! {fn apply_index(&self, index: usize) -> usize} +// dispatch! {fn unapply_indices(&self, indices: &[usize]) -> Vec} +// dispatch! {fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize} +// dispatch! {fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize) -> usize} +// dispatch! {fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec)} +// dispatch! {fn apply_many(&self, index: usize, coordinates: &[f64], dim: usize) -> (usize, Vec)} +//} +// +//impl WithStride for Operator { +// dispatch! {fn stride(&mut self) -> usize} +// dispatch! {fn increment_stride(&mut self, amount: usize)} +// dispatch! {fn decrement_stride(&mut self, amount: usize)} +//} +// +//impl WithOffset for Operator { +// dispatch! {fn offset(&mut self) -> usize} +// dispatch! {fn increment_offset(&mut self, amount: usize)} +// dispatch! {fn decrement_offset(&mut self, amount: usize)} +//} +// +// +//trait IndexOperator { +// /// Returns the difference between the dimension of the input and the output sequence. +// fn delta_dim(&self) -> usize; +// fn delta_len(&self) -> (usize, usize); +// /// Map the index. +// fn apply_index(&self, index: usize) -> usize; +// /// Returns all indices that map to the given indices. TODO: examples with take and children? +// fn unapply_indices(&self, indices: &[usize]) -> Vec; +// /// Map the index and coordinate of an element in the output sequence to +// /// the input sequence. The index is returned, the coordinate is adjusted +// /// in-place. If the coordinate dimension of the input sequence is larger +// /// than that of the output sequence, the [Operator::delta_dim()] last +// /// elements of the coordinate are discarded. +//} +// +//impl UnsizedSequence for IndexOperator { +// fn delta_dim(&self) -> usize { +// self.delta_dim() +// } +// fn delta_len(&self) -> (usize, usize) { +// self.delta_len() +// } +// fn apply_index(&self, index: usize) -> usize { +// self.apply_index(index) +// } +// fn unapply_indices(&self, indices: &[usize]) -> Vec { +// self.unapply_indices(indices) +// } +// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { +// self.apply_index(index) +// } +// fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize, stride: usize) -> usize { +// self.apply_index(index) +// } +//} +// +//impl WithOffset for IndexOperator { +// fn offset(&self) -> usize { +// 0 +// } +// fn increment_offset(&self, _amount: usize) {} +// fn decrement_offset(&self, _amount: usize) {} +//} +// +// +// +// +// +//impl DescribeOperator for Operator { +// dispatch! {fn operator_kind(&self) -> OperatorKind} +// dispatch! {fn as_children(&self) -> Option<&Children>} +// dispatch! {fn as_children_mut(&mut self) -> Option<&mut Children>} +// dispatch! {fn as_edges(&self) -> Option<&Edges>} +// dispatch! {fn as_edges_mut(&mut self) -> Option<&mut Edges>} +//} +// +//impl std::fmt::Debug for Operator { +// dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result} //} // //#[derive(Debug, Clone, PartialEq)] -//enum NewAbsoluteChainsError { -// DeltaDimensionMismatch, +//enum OperatorKind { +// Index(usize, usize), +// Coordinate(usize, usize, usize, usize), +// Other, +//} +// +//trait DescribeOperator { +// /// Returns the kind of operation the operator applies to its parent sequence. +// fn operator_kind(&self) -> OperatorKind; +// #[inline] +// fn as_children(&self) -> Option<&Children> { +// None +// } +// #[inline] +// fn as_children_mut(&mut self) -> Option<&mut Children> { +// None +// } +// #[inline] +// fn as_edges(&self) -> Option<&Edges> { +// None +// } +// #[inline] +// fn as_edges_mut(&mut self) -> Option<&mut Edges> { +// None +// } //} // -//impl std::error::Error for NewAbsoluteChainsError {} +//// TODO: add StrictMonotonicIncreasingTake // -//impl std::fmt::Display for NewAbsoluteChainsError { -// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -// std::fmt::Debug::fmt(self, f) +//#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +//pub struct Take { +// indices: Box<[usize]>, +// len: usize, +// stride: usize, +//} +// +//impl Take { +// #[inline] +// pub fn new(indices: impl Into>, len: usize) -> Self { +// Self { +// indices: indices.into(), +// len, +// stride: 1, +// } // } //} // -//impl AbsoluteChains { -// pub fn new( -// tip_dim: Dim, -// delta_dim: Dim, -// chains: Vec<(usize, Chain)>, -// ) -> Result { -// if chains -// .iter() -// .any(|(_, chain)| chain.delta_dim() != delta_dim) -// { -// Err(NewAbsoluteChainsError::DeltaDimensionMismatch) -// } else { -// Ok(Self { -// tip_dim, -// delta_dim, -// chains, -// }) +//#[inline] +//fn divmod(x: usize, y: usize) -> (usize, usize) { +// (x / y, x % y) +//} +// +//// macro_rules! unflatten_index { +//// ($rem:expr $(, $index:ident)*; $($len:expr,)* $len:expr) => {{ +//// let (rem, index) = divmod($rem, $len); +//// unflatten_index!($rem, index $(, $index)*; $($len),*) +//// }}; +//// } +//// +//// macro_rules! flatten_indices { +//// ($flat:expr;) => {$flat}; +//// ($flat:expr, $index:expr $(, $index:expr)*; $len:expr $(, $len:expr)*) => { +//// flatten_indices!(($flat + $index) * $len $(, $index)*; $($len),*) +//// } +//// } +// +//trait LenInOut { +// fn len_in(&self) -> usize; +// fn len_out(&self) -> usize; +//} +// +//impl LenInOut for usize { +// fn len_in(&self) -> { +// *self +// } +// fn len_out(&self) -> { +// *self +// } +//} +// +//impl LenInOut for [usize; 2] { +// fn len_in(&self) -> { +// *self[0] +// } +// fn len_out(&self) -> { +// *self[1] +// } +//} +// +//#[inline] +//fn map_sandwiched_index(index: usize, len0: impl LenInOut, len1: impl LenInOut, f: impl FnOnce(usize) -> usize) -> usize { +// let (index, i2 = divmod(index, len1.len_in()); +// let (i0, i1) = divmod(index, len0.len_in()); +// (i0 * len0.len_out() + f(i1)) * len1.len_out() + i2 +//} +// +//#[inline] +//fn map_strided_index(index: usize, stride: usize, f: impl FnOnce(usize) -> usize) -> usize { +// f(index / stride) * stride + (index % stride) +//} +// +//impl UnsizedSequence for Take { +// #[inline] +// fn delta_dim(&self) -> usize { +// 0 +// } +// #[inline] +// fn delta_len(&self) -> (usize, usize) { +// (self.indices.len(), self.len) +// } +// #[inline] +// fn increment_offset(&mut self, _amount: usize) {} +// #[inline] +// fn decrement_offset(&mut self, _amount: usize) {} +// #[inline] +// fn increment_stride(&mut self, amount: usize) { +// self.stride *= amount; +// } +// #[inline] +// fn decrement_stride(&mut self, amount: usize) { +// self.stride /= amount; +// } +// #[inline] +// fn apply_index(&self, index: usize) -> usize { +// map_sandwiched_index(index, [self.indices.len(), self.len], self.stride, |i| self.indices[i]) +// } +// #[inline] +// fn unapply_indices(&self, indices: &[usize]) -> Vec { +// indices.iter().filter_map(|index| { +// let (i0, i1, i2) = unflatten_index!(index; self.len, self.stride); +// self.indices.iter().position(|i| *i == i1).map(|j1| flatten_indices!(i0, j1, i2; self.indices.len(), self.stride)) +// }).collect() +// } +// #[inline] +// fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { +// self.apply_index(index) +// } +// #[inline] +// fn apply_many_inplace(&self, index: usize, _coordinate: &mut [f64], _dim: usize) -> usize { +// self.apply_index(index) +// } +//} +// +//impl DescribeOperator for Take { +// #[inline] +// fn operator_kind(&self) -> OperatorKind { +// OperatorKind::Index(self.len, self.indices.len()) +// } +//} +// +//#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +//pub struct Children { +// simplex: Simplex, +// offset: usize, +// stride: usize, +//} +// +//impl Children { +// #[inline] +// pub fn new(simplex: Simplex, offset: usize) -> Self { +// Self { simplex, offset, stride: 1 } +// } +//} +// +//impl UnsizedSequence for Children { +// #[inline] +// fn delta_dim(&self) -> usize { +// 0 +// } +// #[inline] +// fn delta_len(&self) -> (usize, usize) { +// (self.simplex.nchildren(), 1) +// } +// #[inline] +// fn increment_offset(&mut self, amount: usize) { +// self.offset += amount; +// } +// #[inline] +// fn decrement_offset(&mut self, amount: usize) { +// self.offset -= amount; +// } +// #[inline] +// fn increment_stride(&mut self, amount: usize) { +// self.stride *= amount; +// } +// #[inline] +// fn decrement_stride(&mut self, amount: usize) { +// self.stride /= amount; +// } +// #[inline] +// fn apply_index(&self, index: usize) -> usize { +// map_strided_index(index, self.stride, |i| self.simplex.apply_child_index(i)) +// } +// #[inline] +// fn unapply_indices(&self, indices: &[usize]) -> Vec { +// self.simplex.unapply_child_indices(indices) +// } +// #[inline] +// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { +// self.simplex +// .apply_child(index, &mut coordinate[self.offset as usize..]) +// } +//} +// +//impl DescribeOperator for Children { +// #[inline] +// fn operator_kind(&self) -> OperatorKind { +// OperatorKind::Coordinate( +// self.offset, +// self.simplex.dim(), +// self.simplex.dim(), +// self.simplex.nchildren(), +// ) +// } +// #[inline] +// fn as_children(&self) -> Option<&Children> { +// Some(self) +// } +// #[inline] +// fn as_children_mut(&mut self) -> Option<&mut Children> { +// Some(self) +// } +//} +// +//#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +//pub struct Edges { +// simplex: Simplex, +// offset: usize, +//} +// +//impl Edges { +// #[inline] +// pub fn new(simplex: Simplex, offset: usize) -> Self { +// Self { simplex, offset } +// } +//} +// +//impl UnsizedSequence for Edges { +// #[inline] +// fn delta_dim(&self) -> usize { +// 1 +// } +// #[inline] +// fn delta_len(&self) -> (usize, usize) { +// (self.simplex.nedges(), 1) +// } +// #[inline] +// fn increment_offset(&mut self, amount: usize) { +// self.offset += amount; +// } +// #[inline] +// fn decrement_offset(&mut self, amount: usize) { +// self.offset -= amount; +// } +// #[inline] +// fn increment_stride(&mut self, amount: usize) { +// self.stride *= amount; +// } +// #[inline] +// fn decrement_stride(&mut self, amount: usize) { +// self.stride /= amount; +// } +// #[inline] +// fn apply_index(&self, index: usize) -> usize { +// let i0, i1 = unflatten_index!(index; self.stride); +// flatten_indices!(self.simplex.apply_edge_index(i0), i1; self.stride) +// } +// #[inline] +// fn unapply_indices(&self, indices: &[usize]) -> Vec { +// self.simplex.unapply_edge_indices(indices) +// } +// #[inline] +// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { +// self.simplex.apply_edge(index, &mut coordinate[self.offset as usize..]) +// } +//} +// +//impl DescribeOperator for Edges { +// #[inline] +// fn operator_kind(&self) -> OperatorKind { +// OperatorKind::Coordinate( +// self.offset, +// self.simplex.dim(), +// self.simplex.edge_dim(), +// self.simplex.nedges(), +// ) +// } +// #[inline] +// fn as_edges(&self) -> Option<&Edges> { +// Some(self) +// } +// #[inline] +// fn as_edges_mut(&mut self) -> Option<&mut Edges> { +// Some(self) +// } +//} +// +//#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +//pub struct UniformPoints { +// points: Box<[FiniteF64]>, +// point_dim: usize, +// offset: usize, +//} +// +//impl UniformPoints { +// pub fn new(points: Box<[f64]>, point_dim: usize, offset: usize) -> Self { +// // TODO: assert that the points are actually finite. +// let points: Box<[FiniteF64]> = unsafe { std::mem::transmute(points) }; +// Self { +// points, +// point_dim, +// offset, // } // } // #[inline] -// pub fn len(&self) -> usize { -// self.chains.iter().map(|(len, _)| len).sum() +// pub const fn npoints(&self) -> usize { +// self.points.len() / self.point_dim as usize // } +//} +// +//impl UnsizedSequence for UniformPoints { // #[inline] -// pub fn root_dim(&self) -> Dim { -// self.tip_dim + self.delta_dim +// fn delta_dim(&self) -> usize { +// self.point_dim // } // #[inline] -// pub fn tip_dim(&self) -> Dim { -// self.tip_dim +// fn delta_len(&self) -> (usize, usize) { +// (self.npoints(), 1) // } -// fn get_chain(&self, mut index: usize) -> Option<(&Chain, usize)> { -// for (len, chain) in self.chains.iter() { -// if index < *len { -// return Some((&chain, index)); +// #[inline] +// fn increment_offset(&mut self, amount: usize) { +// self.offset += amount; +// } +// #[inline] +// fn decrement_offset(&mut self, amount: usize) { +// self.offset -= amount; +// } +// #[inline] +// fn increment_stride(&mut self, amount: usize) { +// self.stride *= amount; +// } +// #[inline] +// fn decrement_stride(&mut self, amount: usize) { +// self.stride /= amount; +// } +// #[inline] +// fn apply_index(&self, index: usize) -> usize { +// index / self.npoints() +// } +// #[inline] +// fn unapply_indices(&self, indices: &[usize]) -> Vec { +// let mut point_indices = Vec::with_capacity(indices.len() * self.npoints()); +// for index in indices.iter() { +// for ipoint in 0..self.npoints() { +// point_indices.push(index * self.npoints() + ipoint); // } -// index -= len; // } -// None +// point_indices // } -// #[inline] -// pub fn apply_inplace(&self, index: usize, coordinate: &mut [f64]) -> Option { -// self.get_chain(index) -// .map(|(chain, index)| chain.apply_many_inplace(index, coordinate, self.root_dim())) +// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { +// let point_dim = self.point_dim as usize; +// let coordinate = &mut coordinate[self.offset as usize..]; +// coordinate.copy_within(..coordinate.len() - point_dim, point_dim); +// let ipoint = index % self.npoints(); +// let offset = ipoint as usize * point_dim; +// let points: &[f64] = +// unsafe { std::mem::transmute(&self.points[offset..offset + point_dim]) }; +// coordinate[..point_dim].copy_from_slice(points); +// index / self.npoints() // } +//} +// +//impl DescribeOperator for UniformPoints { // #[inline] -// pub fn apply(&self, index: usize, coordinates: &[f64]) -> Option<(usize, Vec)> { -// self.get_chain(index) -// .map(|(chain, index)| chain.apply_many(index, coordinates, self.tip_dim())) +// fn operator_kind(&self) -> OperatorKind { +// OperatorKind::Coordinate(self.offset, self.point_dim, 0, self.npoints()) // } //} // -//pub struct AbsoluteChain { -// tip_dim: Dim, -// delta_dim: Dim, -// tip_len: usize, -// chain: Chain, +///// An operator that maps a sequence of elements to another sequence of elements. +///// +///// Given a sequence of elements an [`Operator`] defines a new sequence. For +///// example [`Operator::Children`] gives the sequence of child elements and +///// [`Operator::Take`] gives a subset of the input sequence. +///// +///// All variants of [`Operator`] apply some operation to either every element of +///// the parent sequence, variants [`Operator::Children`], [`Operator::Edges`] +///// and [`Operator::UniformPoints`], or to consecutive chunks of the input +///// sequence, in which case the size of the chunks is included in the variant +///// and the input sequence is assumed to be a multiple of the chunk size long. +//#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +//pub enum Operator { +// Transpose(Transpose), +// /// A subset of a sequence: the input sequence is reshaped to `(_, len)`, +// /// the given `indices` are taken from the last axis and the result is +// /// flattened. +// Take(Take), +// /// The children of a every element of a sequence. +// Children(Children), +// /// The edges of a every element of a sequence. +// Edges(Edges), +// UniformPoints(UniformPoints), //} // -//pub struct RelativeChain { -// tip_dim: Dim, -// delta_dim: Dim, -// order: Vec, -// chains: Vec, +//macro_rules! impl_from_for_operator { +// ($($Variant:ident),*) => {$( +// impl From<$Variant> for Operator { +// fn from(variant: $Variant) -> Self { +// Self::$Variant(variant) +// } +// } +// )*} //} // -//impl RelativeChain { -// pub fn get_chain(&self, index: usize) -> Option<(Chain, usize, usize)> { -// for (offset, indices, -// pub fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { -// self.get_chain(index).map(|(chain, offset, index)| { -// offset + chain.apply_many_inplace(index, coordinates, self.tip_dim()) +//impl_from_for_operator! {Transpose, Take, Children, Edges, UniformPoints} +// +//impl Operator { +// /// Construct a new operator that transposes a sequence of elements. +// pub fn new_transpose(len1: usize, len2: usize) -> Self { +// Transpose::new(len1, len2).into() +// } +// /// Construct a new operator that takes a subset of a sequence of elements. +// pub fn new_take(indices: impl Into>, len: usize) -> Self { +// Take::new(indices, len).into() +// } +// /// Construct a new operator that maps a sequence of elements to its children. +// pub fn new_children(simplex: Simplex, offset: usize) -> Self { +// Children::new(simplex, offset).into() +// } +// /// Construct a new operator that maps a sequence of elements to its edges. +// pub fn new_edges(simplex: Simplex, offset: usize) -> Self { +// Edges::new(simplex, offset).into() +// } +// /// Construct a new operator that adds points to every element of a sequence. +// pub fn new_uniform_points(points: Box<[f64]>, point_dim: usize, offset: usize) -> Self { +// UniformPoints::new(points, point_dim, offset).into() +// } +// pub fn swap(&self, other: &Self) -> Option> { +// let mut other = other.clone(); +// swap(self, &mut other).map(|tail| iter::once(other).chain(tail.into_iter()).collect()) +// } +//} +// +//macro_rules! dispatch { +// ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { +// #[inline] +// $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { +// match $self { +// Operator::Transpose(var) => var.$fn($($arg),*), +// Operator::Take(var) => var.$fn($($arg),*), +// Operator::Children(var) => var.$fn($($arg),*), +// Operator::Edges(var) => var.$fn($($arg),*), +// Operator::UniformPoints(var) => var.$fn($($arg),*), +// } +// } +// }; +// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { +// #[inline] +// $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { +// match $self { +// Operator::Transpose(var) => var.$fn($($arg),*), +// Operator::Take(var) => var.$fn($($arg),*), +// Operator::Children(var) => var.$fn($($arg),*), +// Operator::Edges(var) => var.$fn($($arg),*), +// Operator::UniformPoints(var) => var.$fn($($arg),*), +// } +// } +// }; +//} +// +//impl UnsizedSequence for Operator { +// dispatch! {fn delta_dim(&self) -> usize} +// dispatch! {fn delta_len(&self) -> (usize, usize)} +// dispatch! {fn increment_offset(&mut self, amount: usize)} +// dispatch! {fn decrement_offset(&mut self, amount: usize)} +// dispatch! {fn increment_stride(&mut self, amount: usize)} +// dispatch! {fn decrement_stride(&mut self, amount: usize)} +// dispatch! {fn apply_index(&self, index: usize) -> usize} +// dispatch! {fn unapply_indices(&self, indices: &[usize]) -> Vec} +// dispatch! {fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize} +// dispatch! {fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize) -> usize} +// dispatch! {fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec)} +// dispatch! {fn apply_many(&self, index: usize, coordinates: &[f64], dim: usize) -> (usize, Vec)} +//} +// +//impl DescribeOperator for Operator { +// dispatch! {fn operator_kind(&self) -> OperatorKind} +// dispatch! {fn as_children(&self) -> Option<&Children>} +// dispatch! {fn as_children_mut(&mut self) -> Option<&mut Children>} +// dispatch! {fn as_edges(&self) -> Option<&Edges>} +// dispatch! {fn as_edges_mut(&mut self) -> Option<&mut Edges>} +//} +// +//impl std::fmt::Debug for Operator { +// dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result} +//} +// +//fn swap(l: &L, r: &mut R) -> Option> +//where +// L: DescribeOperator + UnsizedSequence + Into, +// R: DescribeOperator + UnsizedSequence, +//{ +// if let (Some(edges), Some(children)) = (l.as_edges(), r.as_children_mut()) { +// if edges.offset == children.offset && edges.simplex.edge_dim() == children.simplex.dim() { +// let simplex = edges.simplex; +// let indices = simplex.swap_edges_children_map(); +// let take = Operator::new_take(indices, simplex.nchildren() * simplex.nedges()); +// children.simplex = simplex; +// return Some(vec![l.clone().into(), take]); +// } +// } +// use OperatorKind::*; +// match (l.operator_kind(), r.operator_kind()) { +// (Index(1, 1), Coordinate(_, _, _, _)) => Some(vec![l.clone().into()]), +// (Index(l_nout, l_nin), Coordinate(_, _, _, r_gen)) => Some(vec![ +// Operator::new_transpose(r_gen, l_nout), +// l.clone().into(), +// Operator::new_transpose(l_nin, r_gen), +// ]), +// (Coordinate(l_off, _, l_nin, l_gen), Coordinate(r_off, r_nout, _, r_gen)) => { +// if l_off + l_nin <= r_off { +// r.increment_offset(l.delta_dim()); +// Some(vec![ +// l.clone().into(), +// Operator::new_transpose(l_gen, r_gen), +// ]) +// } else if l_off >= r_off + r_nout { +// let mut l = l.clone(); +// l.decrement_offset(r.delta_dim()); +// Some(vec![l.into(), Operator::new_transpose(l_gen, r_gen)]) +// } else { +// None +// } +// } +// _ => None, +// } +//} +// +///// A chain of [`Operator`]s. +//#[derive(Debug, Clone, PartialEq)] +//pub struct UnsizedChain { +// rev_operators: Vec, +//} +// +//impl UnsizedChain { +// #[inline] +// pub fn new(operators: Operators) -> Self +// where +// Operators: IntoIterator, +// Operators::IntoIter: DoubleEndedIterator, +// { +// UnsizedChain { +// rev_operators: operators.into_iter().rev().collect(), +// } +// } +// #[inline] +// pub fn empty() -> Self { +// UnsizedChain { +// rev_operators: Vec::new(), +// } +// } +// pub fn push(&mut self, operator: impl Into) { +// self.rev_operators.insert(0, operator.into()) +// } +// /// Returns a clone of this [`UnsizedChain`] with the given `operator` appended. +// #[inline] +// pub fn clone_and_push(&self, operator: Operator) -> Self { +// Self::new( +// self.rev_operators +// .iter() +// .rev() +// .cloned() +// .chain(iter::once(operator)), +// ) +// } +// #[inline] +// pub fn iter(&self) -> impl Iterator + DoubleEndedIterator { +// self.rev_operators.iter().rev() +// } +// fn split_heads(&self) -> BTreeMap> { +// let mut heads = BTreeMap::new(); +// 'a: for (i, head) in self.rev_operators.iter().enumerate() { +// let mut rev_tail: Vec<_> = self.rev_operators.iter().take(i).cloned().collect(); +// let mut head = head.clone(); +// for op in self.rev_operators.iter().skip(i + 1) { +// if let Some(ops) = swap(op, &mut head) { +// rev_tail.extend(ops.into_iter().rev()); +// } else { +// continue 'a; +// } +// } +// heads.insert(head, rev_tail); +// } +// 'b: for (i, op) in self.rev_operators.iter().enumerate() { +// if let Operator::Edges(Edges { +// simplex: Simplex::Line, +// offset, +// }) = op +// { +// let simplex = Simplex::Line; +// let mut rev_tail: Vec<_> = self.rev_operators.iter().take(i).cloned().collect(); +// let mut head = Operator::new_children(simplex, *offset); +// let indices = simplex.swap_edges_children_map(); +// let take = Operator::new_take(indices, simplex.nchildren() * simplex.nedges()); +// rev_tail.push(take); +// rev_tail.push(op.clone()); +// for op in self.rev_operators.iter().skip(i + 1) { +// if let Some(ops) = swap(op, &mut head) { +// rev_tail.extend(ops.into_iter().rev()); +// } else { +// continue 'b; +// } +// } +// heads.insert(head, rev_tail); +// } +// } +// heads +// } +// /// Remove and return the common prefix of two chains, transforming either if necessary. +// pub fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { +// let mut common = Vec::new(); +// let mut rev_a = self.rev_operators.clone(); +// let mut rev_b = other.rev_operators.clone(); +// let mut i = 0; +// while !rev_a.is_empty() && !rev_b.is_empty() { +// i += 1; +// if i > 10 { +// break; +// } +// if rev_a.last() == rev_b.last() { +// common.push(rev_a.pop().unwrap()); +// rev_b.pop(); +// continue; +// } +// let heads_a = UnsizedChain::new(rev_a.iter().rev().cloned()).split_heads(); +// let heads_b = UnsizedChain::new(rev_b.iter().rev().cloned()).split_heads(); +// if let Some((head, a, b)) = heads_a +// .iter() +// .filter_map(|(h, a)| heads_b.get(h).map(|b| (h, a, b))) +// .min_by_key(|(_, a, b)| std::cmp::max(a.len(), b.len())) +// { +// common.push(head.clone()); +// rev_a = a.clone(); +// rev_b = b.clone(); +// continue; +// } +// break; +// } +// let common = if rev_a.is_empty() +// && (!rev_b.is_empty() || self.rev_operators.len() <= other.rev_operators.len()) +// { +// self.clone() +// } else if rev_b.is_empty() { +// other.clone() +// } else { +// Self::new(common) +// }; +// ( +// common, +// Self::new(rev_a.into_iter().rev()), +// Self::new(rev_b.into_iter().rev()), +// ) +// } +//} +// +//impl UnsizedSequence for UnsizedChain { +// #[inline] +// fn delta_dim(&self) -> usize { +// self.rev_operators.iter().map(|op| op.delta_dim()).sum() +// } +// #[inline] +// fn delta_len(&self) -> (usize, usize) { +// self.rev_operators.iter().rfold((1, 1), |(n, d), op| { +// let (opn, opd) = op.delta_len(); +// (n * opn, d * opd) // }) // } -// pub fn apply(&self, index: usize, coordinates: &[f64]) -> Option<(usize, Vec)> { -// self.get_chain(index).map(|(chain, offset, index)| { -// offset + chain.apply_many(index, coordinates, self.tip_dim()) +// #[inline] +// fn increment_offset(&mut self, amount: usize) { +// for op in self.rev_operators.iter_mut() { +// op.increment_offset(amount); +// } +// } +// #[inline] +// fn decrement_offset(&mut self, amount: usize) { +// for op in self.rev_operators.iter_mut() { +// op.decrement_offset(amount); +// } +// } +// #[inline] +// fn increment_stride(&mut self, amount: usize) { +// for op in self.rev_operators.iter_mut() { +// op.increment_stride(amount); +// } +// } +// #[inline] +// fn decrement_stride(&mut self, amount: usize) { +// for op in self.rev_operators.iter_mut() { +// op.decrement_stride(amount); +// } +// } +// #[inline] +// fn apply_index(&self, index: usize) -> usize { +// self.rev_operators +// .iter() +// .fold(index, |index, op| op.apply_index(index)) +// } +// #[inline] +// fn unapply_indices(&self, indices: &[usize]) -> Vec { +// let indices = indices.iter().cloned().collect(); +// self.rev_operators +// .iter() +// .rev() +// .fold(indices, |indices, op| op.unapply_indices(&indices)) +// } +// #[inline] +// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { +// self.rev_operators +// .iter() +// .fold(index, |index, op| op.apply_one_inplace(index, coordinate)) +// } +// #[inline] +// fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize) -> usize { +// self.rev_operators.iter().fold(index, |index, op| { +// op.apply_many_inplace(index, coordinates, dim) // }) // } //} - -#[derive(Debug, Clone)] -pub struct Topology { - transforms: UnsizedChain, - dim: Dim, - root_len: usize, - len: usize, -} - -impl Topology { - pub fn new(dim: Dim, len: usize) -> Self { - Self { - transforms: UnsizedChain::new([]), - dim, - root_len: len, - len, - } - } - pub fn derive(&self, operator: Operator) -> Self { - let (n, d) = operator.delta_len(); - Self { - root_len: self.root_len, - len: self.len * n / d, - dim: self.dim - operator.delta_dim(), - transforms: self.transforms.clone_and_push(operator), - } - } -} - -impl Mul for &Topology { - type Output = Topology; - - fn mul(self, other: &Topology) -> Topology { - Topology { - transforms: UnsizedChain::new( - iter::once(Operator::new_transpose(other.root_len, self.root_len)) - .chain(self.transforms.iter().cloned()) - .chain(iter::once(Operator::new_transpose( - self.len, - other.root_len, - ))) - .chain(other.transforms.iter().map(|op| { - let mut op = op.clone(); - op.increment_offset(self.dim); - op - })), - ), - dim: self.dim + other.dim, - root_len: self.root_len * other.root_len, - len: self.len * other.len, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use approx::assert_abs_diff_eq; - use Simplex::*; - - #[test] - fn unapply_indices_offset() { - let offset = Offset::new(4); - assert_eq!(offset.unapply_indices(&[3, 4, 6]), vec![0, 2]); - assert_eq!(offset.unapply_indices(&[3]), vec![]); - } - - #[test] - fn unapply_indices_transpose() { - let transpose = Transpose::new(2, 3); - assert_eq!(transpose.unapply_indices(&[0, 1, 3, 8, 13, 14]), vec![0, 3, 4, 7, 15, 13]); - } - - #[test] - fn unapply_indices_take() { - let take = Take::new([4, 1], 5); - assert_eq!(take.unapply_indices(&[0, 1, 4, 5, 8, 9]), vec![1, 0, 2]); - assert_eq!(take.unapply_indices(&[0]), vec![]); - } - - #[test] - fn unapply_indices_children() { - let children = Children::new(Triangle, 0); - assert_eq!(children.unapply_indices(&[0, 2]), vec![0, 1, 2, 3, 8, 9, 10, 11]); - } - - #[test] - fn unapply_indices_edges() { - let edges = Edges::new(Triangle, 0); - assert_eq!(edges.unapply_indices(&[0, 2]), vec![0, 1, 2, 6, 7, 8]); - } - - #[test] - fn unapply_indices_uniform_points() { - let points = UniformPoints::new(Box::new([0.0, 0.0, 1.0, 0.0, 0.0, 1.0]), 2, 0); - assert_eq!(points.unapply_indices(&[0, 2]), vec![0, 1, 2, 6, 7, 8]); - } - - macro_rules! assert_eq_op_apply { - ($op:expr, $ii:expr, $ic:expr, $oi:expr, $oc:expr) => {{ - let ic = $ic; - let oc = $oc; - let mut work = oc.clone(); - for i in 0..ic.len() { - work[i] = ic[i]; - } - for i in ic.len()..oc.len() { - work[i] = 0.0; - } - assert_eq!($op.apply_one_inplace($ii, &mut work), $oi); - assert_abs_diff_eq!(work[..], oc[..]); - }}; - } - - #[test] - fn apply_children_line() { - let op = Operator::new_children(Line, 0); - assert_eq_op_apply!(op, 0 * 2 + 0, [0.0], 0, [0.0]); - assert_eq_op_apply!(op, 1 * 2 + 0, [1.0], 1, [0.5]); - assert_eq_op_apply!(op, 2 * 2 + 1, [0.0], 2, [0.5]); - assert_eq_op_apply!(op, 3 * 2 + 1, [1.0], 3, [1.0]); - assert_eq_op_apply!(op, 0, [0.0, 2.0], 0, [0.0, 2.0]); - assert_eq_op_apply!(op, 1, [0.0, 3.0, 4.0], 0, [0.5, 3.0, 4.0]); - let op = Operator::new_children(Line, 1); - assert_eq_op_apply!(op, 1, [2.0, 0.0], 0, [2.0, 0.5]); - assert_eq_op_apply!(op, 1, [3.0, 0.0, 4.0], 0, [3.0, 0.5, 4.0]); - } - - #[test] - fn apply_edges_line() { - let op = Operator::new_edges(Line, 0); - assert_eq_op_apply!(op, 0, [], 0, [1.0]); - assert_eq_op_apply!(op, 3, [], 1, [0.0]); - assert_eq_op_apply!(op, 4, [], 2, [1.0]); - assert_eq_op_apply!(op, 7, [], 3, [0.0]); - assert_eq_op_apply!(op, 0, [2.0], 0, [1.0, 2.0]); - assert_eq_op_apply!(op, 1, [3.0, 4.0], 0, [0.0, 3.0, 4.0]); - let op = Operator::new_edges(Line, 1); - assert_eq_op_apply!(op, 0, [2.0], 0, [2.0, 1.0]); - assert_eq_op_apply!(op, 0, [3.0, 4.0], 0, [3.0, 1.0, 4.0]); - } - - // #[test] - // fn apply_edges_square() { - // let op = Operator::Edges { - // simplices: Box::new([Line, Line]), - // offset: 0, - // }; - // assert_eq!(op.apply(0 * 4 + 0, &[0.0]), (0, vec![1.0, 0.0])); - // assert_eq!(op.apply(1 * 4 + 0, &[1.0]), (1, vec![1.0, 1.0])); - // assert_eq!(op.apply(2 * 4 + 1, &[0.0]), (2, vec![0.0, 0.0])); - // assert_eq!(op.apply(3 * 4 + 1, &[1.0]), (3, vec![0.0, 1.0])); - // assert_eq!(op.apply(4 * 4 + 2, &[0.0]), (4, vec![0.0, 1.0])); - // assert_eq!(op.apply(5 * 4 + 2, &[1.0]), (5, vec![1.0, 1.0])); - // assert_eq!(op.apply(6 * 4 + 3, &[0.0]), (6, vec![0.0, 0.0])); - // assert_eq!(op.apply(7 * 4 + 3, &[1.0]), (7, vec![1.0, 0.0])); - // assert_eq!(op.apply(0, &[0.0, 2.0]), (0, vec![1.0, 0.0, 2.0])); - // assert_eq!(op.apply(1, &[0.0, 3.0, 4.0]), (0, vec![0.0, 0.0, 3.0, 4.0])); - // } - - #[test] - fn apply_transpose_index() { - let op = Operator::new_transpose(2, 3); - for i in 0..3 { - for j in 0..2 { - for k in 0..3 { - assert_eq!( - op.apply_one((i * 2 + j) * 3 + k, &[]), - ((i * 3 + k) * 2 + j, vec![]) - ); - } - } - } - } - - #[test] - fn apply_take_all() { - let op = Operator::new_take([3, 2, 0, 4, 1], 5); // inverse: [2, 4, 1, 0, 3] - assert_eq_op_apply!(op, 0, [], 3, []); - assert_eq_op_apply!(op, 6, [1.0], 7, [1.0]); - assert_eq_op_apply!(op, 12, [2.0, 3.0], 10, [2.0, 3.0]); - assert_eq_op_apply!(op, 18, [], 19, []); - assert_eq_op_apply!(op, 24, [], 21, []); - } - - #[test] - fn apply_take_some() { - let op = Operator::new_take([4, 0, 1], 5); // inverse: [1, 2, x, x, 0] - assert_eq_op_apply!(op, 0, [], 4, []); - assert_eq_op_apply!(op, 4, [1.0], 5, [1.0]); - assert_eq_op_apply!(op, 8, [2.0, 3.0], 11, [2.0, 3.0]); - } - - #[test] - fn apply_uniform_points() { - let op = Operator::new_uniform_points(Box::new([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]), 2, 0); - assert_eq_op_apply!(op, 0, [], 0, [0.0, 1.0]); - assert_eq_op_apply!(op, 4, [6.0], 1, [2.0, 3.0, 6.0]); - assert_eq_op_apply!(op, 8, [7.0, 8.0], 2, [4.0, 5.0, 7.0, 8.0]); - } - - #[test] - fn mul_topo() { - let xtopo = Topology::new(1, 2).derive(Operator::new_children(Line, 0)); - let ytopo = Topology::new(1, 3).derive(Operator::new_children(Line, 0)); - let xytopo = &xtopo * &ytopo; - assert_eq!(xtopo.len, 4); - assert_eq!(ytopo.len, 6); - assert_eq!(xytopo.len, 24); - assert_eq!(xytopo.root_len, 6); - for i in 0..4 { - for j in 0..6 { - let x = xtopo.transforms.apply_many(i, &[0.0, 0.0, 1.0, 1.0], 1).1; - let y = ytopo.transforms.apply_many(j, &[0.0, 1.0, 0.0, 1.0], 1).1; - let mut xy = Vec::with_capacity(8); - for k in 0..4 { - xy.push(x[k]); - xy.push(y[k]); - } - assert_eq!( - xytopo.transforms.apply_many( - i * 6 + j, - &[0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0], - 2 - ), - ((i / 2) * 3 + j / 2, xy), - ); - } - } - } - - macro_rules! assert_equiv_topo { - ($topo1:expr, $topo2:expr$(, $simplex:ident)*) => { - #[allow(unused_mut)] - let mut topo1 = $topo1.clone(); - #[allow(unused_mut)] - let mut topo2 = $topo2.clone(); - assert_eq!(topo1.dim, topo2.dim, "topos have different dim"); - assert_eq!(topo1.len, topo2.len, "topos have different len"); - assert_eq!(topo1.root_len, topo2.root_len, "topos have different root_len"); - let from_dim = 0$(+$simplex.dim())*; - assert_eq!(topo1.dim, from_dim, "dimension of topo differs from dimension of given simplices"); - let nelems = topo1.len; - $( - let points = Operator::new_uniform_points( - $simplex.vertices().into(), - $simplex.dim(), - 0, - ); - topo1 = topo1.derive(points.clone()); - topo2 = topo2.derive(points.clone()); - )* - let npoints = topo1.len; - let mut coord1: Vec<_> = iter::repeat(0.0).take((topo1.dim + topo1.transforms.delta_dim()) as usize).collect(); - let mut coord2 = coord1.clone(); - for i in 0..topo1.len { - let ielem = i / (npoints / nelems); - assert_eq!( - topo1.transforms.apply_one_inplace(i, &mut coord1), - topo2.transforms.apply_one_inplace(i, &mut coord2), - "topo1 and topo2 map element {ielem} to different root elements" - ); - assert_abs_diff_eq!(coord1[..], coord2[..]); - } - }; - } - - #[test] - fn swap_edges_children_1d() { - let topo1 = Topology::new(1, 3).derive(Operator::new_edges(Line, 0)); - let topo2 = Topology::new(1, 3) - .derive(Operator::new_children(Line, 0)) - .derive(Operator::new_edges(Line, 0)) - .derive(Operator::new_take([2, 1], 4)); - assert_equiv_topo!(topo1, topo2); - } - - #[test] - fn swap_take_children() { - let take = Operator::new_take([2, 3, 1], 5); - let children = Operator::new_children(Line, 0); - let swapped = vec![ - children.clone(), - Operator::new_transpose(2, 5), - take.clone(), - Operator::new_transpose(3, 2), - ]; - let base = Topology::new(1, 5); - assert_eq!(take.swap(&children), Some(swapped.clone())); - assert_equiv_topo!( - base.derive(take).derive(children), - swapped - .iter() - .cloned() - .fold(base.clone(), |t, o| t.derive(o)), - Line - ); - } - - #[test] - fn swap_take_edges() { - let take = Operator::new_take([2, 3, 1], 5); - let edges = Operator::new_edges(Line, 0); - let swapped = vec![ - edges.clone(), - Operator::new_transpose(2, 5), - take.clone(), - Operator::new_transpose(3, 2), - ]; - let base = Topology::new(1, 5); - assert_eq!(take.swap(&edges), Some(swapped.clone())); - assert_equiv_topo!( - base.derive(take).derive(edges), - swapped - .iter() - .cloned() - .fold(base.clone(), |t, o| t.derive(o)) - ); - } - - macro_rules! fn_test_operator_swap { - ($name:ident, $len:expr $(, $simplex:ident)*; $op1:expr, $op2:expr,) => { - #[test] - fn $name() { - let op1: Operator = $op1; - let op2: Operator = $op2; - let swapped = op1.swap(&op2).expect("not swapped"); - println!("op1: {op1:?}"); - println!("op2: {op2:?}"); - println!("swapped: {swapped:?}"); - let root_dim = op1.delta_dim() + op2.delta_dim() $(+ $simplex.dim())*; - let base = Topology::new(root_dim, 1); - let topo1 = [op1, op2].iter().fold(base.clone(), |t, o| t.derive(o.clone())); - let topo2 = swapped.iter().fold(base, |t, o| t.derive(o.clone())); - let len = $len; - assert_eq!(topo1.len, len, "unswapped topo has unexpected length"); - assert_eq!(topo2.len, len, "swapped topo has unexpected length"); - assert_equiv_topo!(topo1, topo2 $(, $simplex)*); - } - } - } - - fn_test_operator_swap! { - swap_edges_children_triangle1, 6, Line, Line; - Operator::new_edges(Triangle, 0), - Operator::new_children(Line, 0), - } - - fn_test_operator_swap! { - swap_unoverlapping_children_lt_children, 8, Triangle, Line; - Operator::new_children(Triangle, 0), - Operator::new_children(Line, 2), - } - - fn_test_operator_swap! { - swap_unoverlapping_children_gt_children, 8, Line, Triangle; - Operator::new_children(Line, 2), - Operator::new_children(Triangle, 0), - } - - fn_test_operator_swap! { - swap_unoverlapping_edges_lt_children, 6, Line, Line; - Operator::new_edges(Triangle, 0), - Operator::new_children(Line, 1), - } - - fn_test_operator_swap! { - swap_unoverlapping_edges_gt_children, 6, Line, Line; - Operator::new_edges(Triangle, 1), - Operator::new_children(Line, 0), - } - - fn_test_operator_swap! { - swap_unoverlapping_children_lt_edges, 6, Line, Line; - Operator::new_children(Line, 0), - Operator::new_edges(Triangle, 1), - } - - fn_test_operator_swap! { - swap_unoverlapping_children_gt_edges, 6, Line, Line; - Operator::new_children(Line, 2), - Operator::new_edges(Triangle, 0), - } - - fn_test_operator_swap! { - swap_unoverlapping_edges_lt_edges, 6, Line; - Operator::new_edges(Line, 0), - Operator::new_edges(Triangle, 0), - } - - fn_test_operator_swap! { - swap_unoverlapping_edges_gt_edges, 6, Line; - Operator::new_edges(Line, 2), - Operator::new_edges(Triangle, 0), - } - - #[test] - fn split_heads() { - let chain = UnsizedChain::new([ - Operator::new_edges(Triangle, 1), - Operator::new_children(Line, 0), - Operator::new_edges(Line, 2), - Operator::new_children(Line, 1), - Operator::new_children(Line, 0), - ]); - let desired = chain - .iter() - .cloned() - .fold(Topology::new(4, 1), |topo, op| topo.derive(op)); - for (head, tail) in chain.split_heads().into_iter() { - let actual = iter::once(head) - .chain(tail.into_iter().rev()) - .fold(Topology::new(4, 1), |topo, op| topo.derive(op)); - assert_equiv_topo!(actual, desired, Line, Line); - } - } - - #[test] - fn remove_common_prefix() { - let a = UnsizedChain::new([ - Operator::new_children(Line, 0), - Operator::new_children(Line, 0), - ]); - let b = UnsizedChain::new([Operator::new_edges(Line, 0)]); - assert_eq!( - a.remove_common_prefix(&b), - ( - UnsizedChain::new([ - Operator::new_children(Line, 0), - Operator::new_children(Line, 0) - ]), - UnsizedChain::new([]), - UnsizedChain::new([ - Operator::new_edges(Line, 0), - Operator::new_take([2, 1], 4), - Operator::new_take([2, 1], 4) - ]), - ) - ); - } -} +// +//impl IntoIterator for UnsizedChain { +// type Item = Operator; +// type IntoIter = std::vec::IntoIter; +// +// fn into_iter(self) -> Self::IntoIter { +// self.rev_operators.into_iter() +// } +//} +// +//impl FromIterator for UnsizedChain { +// fn from_iter(iter: T) -> Self +// where +// T: IntoIterator, +// { +// let ops: Vec<_> = iter.into_iter().collect(); +// UnsizedChain::new(ops) +// } +//} +// +//#[derive(Debug, Clone)] +//pub struct Topology { +// transforms: UnsizedChain, +// dim: usize, +// root_len: usize, +// len: usize, +//} +// +//impl Topology { +// pub fn new(dim: usize, len: usize) -> Self { +// Self { +// transforms: UnsizedChain::new([]), +// dim, +// root_len: len, +// len, +// } +// } +// pub fn derive(&self, operator: Operator) -> Self { +// let (n, d) = operator.delta_len(); +// Self { +// root_len: self.root_len, +// len: self.len * n / d, +// dim: self.dim - operator.delta_dim(), +// transforms: self.transforms.clone_and_push(operator), +// } +// } +//} +// +//impl Mul for &Topology { +// type Output = Topology; +// +// fn mul(self, other: &Topology) -> Topology { +// Topology { +// transforms: UnsizedChain::new( +// iter::once(Operator::new_transpose(other.root_len, self.root_len)) +// .chain(self.transforms.iter().cloned()) +// .chain(iter::once(Operator::new_transpose( +// self.len, +// other.root_len, +// ))) +// .chain(other.transforms.iter().map(|op| { +// let mut op = op.clone(); +// op.increment_offset(self.dim); +// op +// })), +// ), +// dim: self.dim + other.dim, +// root_len: self.root_len * other.root_len, +// len: self.len * other.len, +// } +// } +//} +// +// +// +// +// macro_rules! assert_eq_op_apply { +// ($op:expr, $ii:expr, $ic:expr, $oi:expr, $oc:expr) => {{ +// let ic = $ic; +// let oc = $oc; +// let mut work = oc.clone(); +// for i in 0..ic.len() { +// work[i] = ic[i]; +// } +// for i in ic.len()..oc.len() { +// work[i] = 0.0; +// } +// assert_eq!($op.apply_one_inplace($ii, &mut work), $oi); +// assert_abs_diff_eq!(work[..], oc[..]); +// }}; +// } +// +// #[test] +// fn apply_children_line() { +// let op = Operator::new_children(Line, 0); +// assert_eq_op_apply!(op, 0 * 2 + 0, [0.0], 0, [0.0]); +// assert_eq_op_apply!(op, 1 * 2 + 0, [1.0], 1, [0.5]); +// assert_eq_op_apply!(op, 2 * 2 + 1, [0.0], 2, [0.5]); +// assert_eq_op_apply!(op, 3 * 2 + 1, [1.0], 3, [1.0]); +// assert_eq_op_apply!(op, 0, [0.0, 2.0], 0, [0.0, 2.0]); +// assert_eq_op_apply!(op, 1, [0.0, 3.0, 4.0], 0, [0.5, 3.0, 4.0]); +// let op = Operator::new_children(Line, 1); +// assert_eq_op_apply!(op, 1, [2.0, 0.0], 0, [2.0, 0.5]); +// assert_eq_op_apply!(op, 1, [3.0, 0.0, 4.0], 0, [3.0, 0.5, 4.0]); +// } +// +// #[test] +// fn apply_edges_line() { +// let op = Operator::new_edges(Line, 0); +// assert_eq_op_apply!(op, 0, [], 0, [1.0]); +// assert_eq_op_apply!(op, 3, [], 1, [0.0]); +// assert_eq_op_apply!(op, 4, [], 2, [1.0]); +// assert_eq_op_apply!(op, 7, [], 3, [0.0]); +// assert_eq_op_apply!(op, 0, [2.0], 0, [1.0, 2.0]); +// assert_eq_op_apply!(op, 1, [3.0, 4.0], 0, [0.0, 3.0, 4.0]); +// let op = Operator::new_edges(Line, 1); +// assert_eq_op_apply!(op, 0, [2.0], 0, [2.0, 1.0]); +// assert_eq_op_apply!(op, 0, [3.0, 4.0], 0, [3.0, 1.0, 4.0]); +// } +// +// // #[test] +// // fn apply_edges_square() { +// // let op = Operator::Edges { +// // simplices: Box::new([Line, Line]), +// // offset: 0, +// // }; +// // assert_eq!(op.apply(0 * 4 + 0, &[0.0]), (0, vec![1.0, 0.0])); +// // assert_eq!(op.apply(1 * 4 + 0, &[1.0]), (1, vec![1.0, 1.0])); +// // assert_eq!(op.apply(2 * 4 + 1, &[0.0]), (2, vec![0.0, 0.0])); +// // assert_eq!(op.apply(3 * 4 + 1, &[1.0]), (3, vec![0.0, 1.0])); +// // assert_eq!(op.apply(4 * 4 + 2, &[0.0]), (4, vec![0.0, 1.0])); +// // assert_eq!(op.apply(5 * 4 + 2, &[1.0]), (5, vec![1.0, 1.0])); +// // assert_eq!(op.apply(6 * 4 + 3, &[0.0]), (6, vec![0.0, 0.0])); +// // assert_eq!(op.apply(7 * 4 + 3, &[1.0]), (7, vec![1.0, 0.0])); +// // assert_eq!(op.apply(0, &[0.0, 2.0]), (0, vec![1.0, 0.0, 2.0])); +// // assert_eq!(op.apply(1, &[0.0, 3.0, 4.0]), (0, vec![0.0, 0.0, 3.0, 4.0])); +// // } +// +// #[test] +// fn apply_transpose_index() { +// let op = Operator::new_transpose(2, 3); +// for i in 0..3 { +// for j in 0..2 { +// for k in 0..3 { +// assert_eq!( +// op.apply_one((i * 2 + j) * 3 + k, &[]), +// ((i * 3 + k) * 2 + j, vec![]) +// ); +// } +// } +// } +// } +// +// #[test] +// fn apply_take_all() { +// let op = Operator::new_take([3, 2, 0, 4, 1], 5); // inverse: [2, 4, 1, 0, 3] +// assert_eq_op_apply!(op, 0, [], 3, []); +// assert_eq_op_apply!(op, 6, [1.0], 7, [1.0]); +// assert_eq_op_apply!(op, 12, [2.0, 3.0], 10, [2.0, 3.0]); +// assert_eq_op_apply!(op, 18, [], 19, []); +// assert_eq_op_apply!(op, 24, [], 21, []); +// } +// +// #[test] +// fn apply_take_some() { +// let op = Operator::new_take([4, 0, 1], 5); // inverse: [1, 2, x, x, 0] +// assert_eq_op_apply!(op, 0, [], 4, []); +// assert_eq_op_apply!(op, 4, [1.0], 5, [1.0]); +// assert_eq_op_apply!(op, 8, [2.0, 3.0], 11, [2.0, 3.0]); +// } +// +// #[test] +// fn apply_uniform_points() { +// let op = Operator::new_uniform_points(Box::new([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]), 2, 0); +// assert_eq_op_apply!(op, 0, [], 0, [0.0, 1.0]); +// assert_eq_op_apply!(op, 4, [6.0], 1, [2.0, 3.0, 6.0]); +// assert_eq_op_apply!(op, 8, [7.0, 8.0], 2, [4.0, 5.0, 7.0, 8.0]); +// } +// +// #[test] +// fn mul_topo() { +// let xtopo = Topology::new(1, 2).derive(Operator::new_children(Line, 0)); +// let ytopo = Topology::new(1, 3).derive(Operator::new_children(Line, 0)); +// let xytopo = &xtopo * &ytopo; +// assert_eq!(xtopo.len, 4); +// assert_eq!(ytopo.len, 6); +// assert_eq!(xytopo.len, 24); +// assert_eq!(xytopo.root_len, 6); +// for i in 0..4 { +// for j in 0..6 { +// let x = xtopo.transforms.apply_many(i, &[0.0, 0.0, 1.0, 1.0], 1).1; +// let y = ytopo.transforms.apply_many(j, &[0.0, 1.0, 0.0, 1.0], 1).1; +// let mut xy = Vec::with_capacity(8); +// for k in 0..4 { +// xy.push(x[k]); +// xy.push(y[k]); +// } +// assert_eq!( +// xytopo.transforms.apply_many( +// i * 6 + j, +// &[0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0], +// 2 +// ), +// ((i / 2) * 3 + j / 2, xy), +// ); +// } +// } +// } +// +// macro_rules! assert_equiv_topo { +// ($topo1:expr, $topo2:expr$(, $simplex:ident)*) => { +// #[allow(unused_mut)] +// let mut topo1 = $topo1.clone(); +// #[allow(unused_mut)] +// let mut topo2 = $topo2.clone(); +// assert_eq!(topo1.dim, topo2.dim, "topos have different dim"); +// assert_eq!(topo1.len, topo2.len, "topos have different len"); +// assert_eq!(topo1.root_len, topo2.root_len, "topos have different root_len"); +// let from_dim = 0$(+$simplex.dim())*; +// assert_eq!(topo1.dim, from_dim, "dimension of topo differs from dimension of given simplices"); +// let nelems = topo1.len; +// $( +// let points = Operator::new_uniform_points( +// $simplex.vertices().into(), +// $simplex.dim(), +// 0, +// ); +// topo1 = topo1.derive(points.clone()); +// topo2 = topo2.derive(points.clone()); +// )* +// let npoints = topo1.len; +// let mut coord1: Vec<_> = iter::repeat(0.0).take((topo1.dim + topo1.transforms.delta_dim()) as usize).collect(); +// let mut coord2 = coord1.clone(); +// for i in 0..topo1.len { +// let ielem = i / (npoints / nelems); +// assert_eq!( +// topo1.transforms.apply_one_inplace(i, &mut coord1), +// topo2.transforms.apply_one_inplace(i, &mut coord2), +// "topo1 and topo2 map element {ielem} to different root elements" +// ); +// assert_abs_diff_eq!(coord1[..], coord2[..]); +// } +// }; +// } +// +// #[test] +// fn swap_edges_children_1d() { +// let topo1 = Topology::new(1, 3).derive(Operator::new_edges(Line, 0)); +// let topo2 = Topology::new(1, 3) +// .derive(Operator::new_children(Line, 0)) +// .derive(Operator::new_edges(Line, 0)) +// .derive(Operator::new_take([2, 1], 4)); +// assert_equiv_topo!(topo1, topo2); +// } +// +// #[test] +// fn swap_take_children() { +// let take = Operator::new_take([2, 3, 1], 5); +// let children = Operator::new_children(Line, 0); +// let swapped = vec![ +// children.clone(), +// Operator::new_transpose(2, 5), +// take.clone(), +// Operator::new_transpose(3, 2), +// ]; +// let base = Topology::new(1, 5); +// assert_eq!(take.swap(&children), Some(swapped.clone())); +// assert_equiv_topo!( +// base.derive(take).derive(children), +// swapped +// .iter() +// .cloned() +// .fold(base.clone(), |t, o| t.derive(o)), +// Line +// ); +// } +// +// #[test] +// fn swap_take_edges() { +// let take = Operator::new_take([2, 3, 1], 5); +// let edges = Operator::new_edges(Line, 0); +// let swapped = vec![ +// edges.clone(), +// Operator::new_transpose(2, 5), +// take.clone(), +// Operator::new_transpose(3, 2), +// ]; +// let base = Topology::new(1, 5); +// assert_eq!(take.swap(&edges), Some(swapped.clone())); +// assert_equiv_topo!( +// base.derive(take).derive(edges), +// swapped +// .iter() +// .cloned() +// .fold(base.clone(), |t, o| t.derive(o)) +// ); +// } +// +// macro_rules! fn_test_operator_swap { +// ($name:ident, $len:expr $(, $simplex:ident)*; $op1:expr, $op2:expr,) => { +// #[test] +// fn $name() { +// let op1: Operator = $op1; +// let op2: Operator = $op2; +// let swapped = op1.swap(&op2).expect("not swapped"); +// println!("op1: {op1:?}"); +// println!("op2: {op2:?}"); +// println!("swapped: {swapped:?}"); +// let root_dim = op1.delta_dim() + op2.delta_dim() $(+ $simplex.dim())*; +// let base = Topology::new(root_dim, 1); +// let topo1 = [op1, op2].iter().fold(base.clone(), |t, o| t.derive(o.clone())); +// let topo2 = swapped.iter().fold(base, |t, o| t.derive(o.clone())); +// let len = $len; +// assert_eq!(topo1.len, len, "unswapped topo has unexpected length"); +// assert_eq!(topo2.len, len, "swapped topo has unexpected length"); +// assert_equiv_topo!(topo1, topo2 $(, $simplex)*); +// } +// } +// } +// +// fn_test_operator_swap! { +// swap_edges_children_triangle1, 6, Line, Line; +// Operator::new_edges(Triangle, 0), +// Operator::new_children(Line, 0), +// } +// +// fn_test_operator_swap! { +// swap_unoverlapping_children_lt_children, 8, Triangle, Line; +// Operator::new_children(Triangle, 0), +// Operator::new_children(Line, 2), +// } +// +// fn_test_operator_swap! { +// swap_unoverlapping_children_gt_children, 8, Line, Triangle; +// Operator::new_children(Line, 2), +// Operator::new_children(Triangle, 0), +// } +// +// fn_test_operator_swap! { +// swap_unoverlapping_edges_lt_children, 6, Line, Line; +// Operator::new_edges(Triangle, 0), +// Operator::new_children(Line, 1), +// } +// +// fn_test_operator_swap! { +// swap_unoverlapping_edges_gt_children, 6, Line, Line; +// Operator::new_edges(Triangle, 1), +// Operator::new_children(Line, 0), +// } +// +// fn_test_operator_swap! { +// swap_unoverlapping_children_lt_edges, 6, Line, Line; +// Operator::new_children(Line, 0), +// Operator::new_edges(Triangle, 1), +// } +// +// fn_test_operator_swap! { +// swap_unoverlapping_children_gt_edges, 6, Line, Line; +// Operator::new_children(Line, 2), +// Operator::new_edges(Triangle, 0), +// } +// +// fn_test_operator_swap! { +// swap_unoverlapping_edges_lt_edges, 6, Line; +// Operator::new_edges(Line, 0), +// Operator::new_edges(Triangle, 0), +// } +// +// fn_test_operator_swap! { +// swap_unoverlapping_edges_gt_edges, 6, Line; +// Operator::new_edges(Line, 2), +// Operator::new_edges(Triangle, 0), +// } +// +// #[test] +// fn split_heads() { +// let chain = UnsizedChain::new([ +// Operator::new_edges(Triangle, 1), +// Operator::new_children(Line, 0), +// Operator::new_edges(Line, 2), +// Operator::new_children(Line, 1), +// Operator::new_children(Line, 0), +// ]); +// let desired = chain +// .iter() +// .cloned() +// .fold(Topology::new(4, 1), |topo, op| topo.derive(op)); +// for (head, tail) in chain.split_heads().into_iter() { +// let actual = iter::once(head) +// .chain(tail.into_iter().rev()) +// .fold(Topology::new(4, 1), |topo, op| topo.derive(op)); +// assert_equiv_topo!(actual, desired, Line, Line); +// } +// } +// +// #[test] +// fn remove_common_prefix() { +// let a = UnsizedChain::new([ +// Operator::new_children(Line, 0), +// Operator::new_children(Line, 0), +// ]); +// let b = UnsizedChain::new([Operator::new_edges(Line, 0)]); +// assert_eq!( +// a.remove_common_prefix(&b), +// ( +// UnsizedChain::new([ +// Operator::new_children(Line, 0), +// Operator::new_children(Line, 0) +// ]), +// UnsizedChain::new([]), +// UnsizedChain::new([ +// Operator::new_edges(Line, 0), +// Operator::new_take([2, 1], 4), +// Operator::new_take([2, 1], 4) +// ]), +// ) +// ); +// } +//} diff --git a/src/finite_f64.rs b/src/finite_f64.rs new file mode 100644 index 000000000..0b81f556b --- /dev/null +++ b/src/finite_f64.rs @@ -0,0 +1,23 @@ +use std::cmp::Ordering; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(transparent)] +pub struct FiniteF64(pub f64); + +impl Eq for FiniteF64 {} + +impl PartialOrd for FiniteF64 { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FiniteF64 { + fn cmp(&self, other: &Self) -> Ordering { + if let Some(ord) = self.0.partial_cmp(&other.0) { + ord + } else { + panic!("not finite"); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index c14ef2adc..aea9a783b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ -pub mod simplex; -pub mod types; pub mod chain; -pub mod sequence; +mod finite_f64; +pub mod operator; +pub mod simplex; +//pub mod sequence; diff --git a/src/operator.rs b/src/operator.rs new file mode 100644 index 000000000..c5bf0f7a8 --- /dev/null +++ b/src/operator.rs @@ -0,0 +1,664 @@ +use crate::finite_f64::FiniteF64; +use crate::simplex::Simplex; +use num::Integer; +use std::rc::Rc; + +#[inline] +const fn divmod(x: usize, y: usize) -> (usize, usize) { + (x / y, x % y) +} + +fn coordinates_iter_mut( + flat: &mut [f64], + stride: usize, + offset: usize, + dim_out: usize, + dim_in: usize, +) -> impl Iterator { + flat.chunks_mut(stride).map(move |coord| { + let coord = &mut coord[offset..]; + let delta = dim_out - dim_in; + if delta != 0 { + coord.copy_within(..coord.len() - delta, delta); + } + &mut coord[..dim_out] + }) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Transpose(usize, usize); + +impl Transpose { + #[inline] + pub const fn new(len1: usize, len2: usize) -> Self { + Self(len1, len2) + } + #[inline] + pub const fn len_out(&self) -> usize { + self.0 * self.1 + } + #[inline] + pub const fn len_in(&self) -> usize { + self.0 * self.1 + } + #[inline] + pub fn apply_index(&self, index: usize) -> usize { + let (j, k) = divmod(index, self.1); + let (i, j) = divmod(j, self.0); + (i * self.1 + k) * self.0 + j + } + pub fn unapply_indices(&self, indices: &[usize]) -> Vec { + indices + .iter() + .map(|k| { + let (j, k) = divmod(*k, self.0); + let (i, j) = divmod(j, self.1); + (i * self.0 + k) * self.1 + j + }) + .collect() + } + #[inline] + const fn is_identity(&self) -> bool { + self.0 == 1 || self.1 == 1 + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IndexOperator { + Transpose(Transpose), + Take { + indices: Rc>, + nindices: usize, + len: usize, + }, +} + +impl IndexOperator { + #[inline] + pub const fn len_out(&self) -> usize { + match self { + Self::Transpose(transpose) => transpose.len_out(), + Self::Take { len, .. } => *len, + } + } + #[inline] + pub const fn len_in(&self) -> usize { + match self { + Self::Transpose(transpose) => transpose.len_in(), + Self::Take { nindices, .. } => *nindices, + } + } + pub fn apply_index(&self, index: usize) -> usize { + match self { + Self::Transpose(transpose) => transpose.apply_index(index), + Self::Take { + indices, + nindices, + len, + } => indices[index % nindices] + index / nindices * len, + } + } + pub fn unapply_indices(&self, indices: &[usize]) -> Vec { + match self { + Self::Transpose(transpose) => transpose.unapply_indices(indices), + Self::Take { + indices: take_indices, + nindices, + len, + } => indices + .iter() + .filter_map(|index| { + let (j, iout) = divmod(*index, *len); + let offset = j * nindices; + take_indices + .iter() + .position(|i| *i == iout) + .map(|iin| offset + iin) + }) + .collect(), + } + } + #[inline] + const fn is_identity(&self) -> bool { + match self { + Self::Transpose(transpose) => transpose.is_identity(), + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CoordinateOperator { + Children(Simplex), + Edges(Simplex), + UniformPoints { + points: Rc>, + npoints: usize, + point_dim: usize, + }, +} + +impl CoordinateOperator { + #[inline] + pub const fn dim_out(&self) -> usize { + match self { + Self::Children(simplex) => simplex.dim(), + Self::Edges(simplex) => simplex.dim(), + Self::UniformPoints { point_dim, .. } => *point_dim, + } + } + #[inline] + pub const fn dim_in(&self) -> usize { + match self { + Self::Children(simplex) => simplex.dim(), + Self::Edges(simplex) => simplex.edge_dim(), + Self::UniformPoints { .. } => 0, + } + } + #[inline] + pub const fn delta_dim(&self) -> usize { + self.dim_out() - self.dim_in() + } + #[inline] + pub const fn len_out(&self) -> usize { + 1 + } + #[inline] + pub const fn len_in(&self) -> usize { + match self { + Self::Children(simplex) => simplex.nchildren(), + Self::Edges(simplex) => simplex.nedges(), + Self::UniformPoints { npoints, .. } => *npoints, + } + } + pub fn apply(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize { + match self { + Self::Children(simplex) => simplex.apply_child(index, coordinates, stride, offset), + Self::Edges(simplex) => simplex.apply_edge(index, coordinates, stride, offset), + Self::UniformPoints { + points, + npoints, + point_dim, + } => { + let dim = *point_dim; + let points: &[f64] = unsafe { std::mem::transmute(&points[..]) }; + let point = &points[(index % npoints) * dim..][..dim]; + for coord in coordinates_iter_mut(coordinates, stride, offset, dim, 0) { + coord.copy_from_slice(point); + } + index / npoints + } + } + } + pub fn unapply_indices(&self, indices: &[usize]) -> Vec { + indices + .iter() + .map(|i| i * self.len_in()) + .flat_map(|i| (0..self.len_in()).map(move |j| i + j)) + .collect() + } + #[inline] + const fn is_identity(&self) -> bool { + false + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Operator { + Index(IndexOperator), + Coordinate(CoordinateOperator, usize), +} + +impl Operator { + pub const fn new_transpose(len1: usize, len2: usize) -> Self { + Self::Index(IndexOperator::Transpose(Transpose::new(len1, len2))) + } + pub fn new_take(indices: impl Into>, len: usize) -> Self { + let indices = Rc::new(indices.into()); + let nindices = indices.len(); + Self::Index(IndexOperator::Take { + indices, + nindices, + len, + }) + } + pub const fn new_children(simplex: Simplex) -> Self { + Self::Coordinate(CoordinateOperator::Children(simplex), 0) + } + pub const fn new_edges(simplex: Simplex) -> Self { + Self::Coordinate(CoordinateOperator::Edges(simplex), 0) + } + pub fn new_uniform_points(points: impl Into>, point_dim: usize) -> Self { + let points: Rc> = Rc::new(unsafe { std::mem::transmute(points.into()) }); + assert_eq!(points.len() % point_dim, 0); + let npoints = points.len() / point_dim; + Self::Coordinate(CoordinateOperator::UniformPoints { + points, + npoints, + point_dim, + }, 0) + } + #[inline] + pub const fn offset(&self) -> usize { + match self { + Self::Index(_) => 0, + Self::Coordinate(_, offset) => *offset, + } + } + #[inline] + pub fn increment_offset(&mut self, amount: usize) { + match self { + Self::Index(_) => {}, + Self::Coordinate(_, offset) => *offset += amount, + } + } + #[inline] + fn with_offset(self, offset: usize) -> Self { + match self { + Self::Coordinate(op, _) => Self::Coordinate(op, offset), + other => other, + } + } + #[inline] + pub const fn dim_out(&self) -> usize { + match self { + Self::Index(_) => 0, + Self::Coordinate(op, _) => op.dim_out(), + } + } + #[inline] + pub const fn dim_in(&self) -> usize { + match self { + Self::Index(_) => 0, + Self::Coordinate(op, _) => op.dim_in(), + } + } + #[inline] + pub const fn delta_dim(&self) -> usize { + self.dim_out() - self.dim_in() + } + #[inline] + pub const fn len_out(&self) -> usize { + match self { + Self::Index(op) => op.len_out(), + Self::Coordinate(op, _) => op.len_out(), + } + } + #[inline] + pub const fn len_in(&self) -> usize { + match self { + Self::Index(op) => op.len_in(), + Self::Coordinate(op, _) => op.len_in(), + } + } + pub fn apply(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { + match self { + Self::Index(op) => op.apply_index(index), + Self::Coordinate(op, offset) => op.apply(index, coordinates, stride, *offset), + } + } + pub fn unapply_indices(&self, indices: &[usize]) -> Vec { + match self { + Self::Index(op) => op.unapply_indices(indices), + Self::Coordinate(op, _) => op.unapply_indices(indices), + } + } + pub fn shift_left(&self, operators: &[Self]) -> Option<(Option, Self, Vec)> { + use CoordinateOperator::{Children, Edges}; + if self.is_transpose() { + return None; + } + let mut target = self.clone(); + let mut shifted_ops: Vec = Vec::new(); + let mut queue: Vec = Vec::new(); + let mut stride_out = 1; + let mut stride_in = 1; + for mut op in operators.iter().rev().cloned() { + // Swap matching edges and children at the same offset. + if let Self::Coordinate(Edges(esimplex), eoffset) = &op { + if let Self::Coordinate(Children(ref mut csimplex), coffset) = &mut target { + if eoffset == coffset && esimplex.edge_dim() == csimplex.dim() { + if stride_in != 1 && self.len_in() != 1 { + shifted_ops.push(Self::new_transpose(stride_in, self.len_in())); + } + shifted_ops.append(&mut queue); + if stride_out != 1 && self.len_in() != 1 { + shifted_ops.push(Self::new_transpose(self.len_in(), stride_out)); + } + shifted_ops.push(Self::new_take(esimplex.swap_edges_children_map(), esimplex.nedges() * esimplex.nchildren())); + shifted_ops.push(Self::Coordinate(Edges(*esimplex), *eoffset)); + *csimplex = *esimplex; + stride_in = 1; + stride_out = 1; + continue; + } + } + } + // Update strides. + if self.len_in() == 1 && self.len_out() == 1 { + } else if self.len_out() == 1 { + let n = stride_out.gcd(&op.len_in()); + stride_out = stride_out / n * op.len_out(); + stride_in *= op.len_in() / n; + } else if let Some(Transpose(ref mut m, ref mut n)) = op.as_transpose_mut() { + if stride_out % (*m * *n) == 0 { + } else if stride_out % *n == 0 && (*m * *n) % (stride_out * self.len_out()) == 0 { + stride_out /= *n; + *m = *m / self.len_out() * self.len_in(); + } else if *n % stride_out == 0 && *n % (stride_out * self.len_out()) == 0 { + stride_out *= *m; + *n = *n / self.len_out() * self.len_in(); + } else { + return None; + } + } else if stride_out % op.len_in() == 0 { + stride_out = stride_out / op.len_in() * op.len_out(); + } else { + return None; + } + // Update offsets. + if let Self::Coordinate(ref mut target, ref mut target_offset) = &mut target { + if let Self::Coordinate(ref mut op, ref mut op_offset) = &mut op { + if *op_offset + op.dim_in() <= *target_offset { + *target_offset += op.delta_dim(); + } else if *target_offset + target.dim_out() <= *op_offset { + *op_offset -= target.delta_dim(); + } else { + return None; + } + } + } + if !op.is_identity() { + queue.push(op); + } + } + if stride_in != 1 && self.len_in() != 1 { + shifted_ops.push(Self::new_transpose(stride_in, self.len_in())); + } + shifted_ops.extend(queue); + if stride_out != 1 && self.len_in() != 1 { + shifted_ops.push(Self::new_transpose(self.len_in(), stride_out)); + } + let leading_transpose = if self.len_out() == 1 || stride_out == 1 { + None + } else { + Some(Transpose::new(stride_out, self.len_out())) + }; + shifted_ops.reverse(); + Some(( + leading_transpose, + target, + shifted_ops, + )) + } + #[inline] + const fn is_identity(&self) -> bool { + match self { + Self::Index(op) => op.is_identity(), + Self::Coordinate(op, _) => op.is_identity(), + } + } + #[inline] + const fn is_transpose(&self) -> bool { + matches!(self, Self::Index(IndexOperator::Transpose(_))) + } + fn as_transpose_mut(&mut self) -> Option<&mut Transpose> { + match self { + Self::Index(IndexOperator::Transpose(ref mut transpose)) => Some(transpose), + _ => None, + } + } +} + +impl From for Operator { + fn from(transpose: Transpose) -> Self { + Self::Index(IndexOperator::Transpose(transpose)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use approx::assert_abs_diff_eq; + use std::iter; + use Simplex::*; + + macro_rules! assert_eq_apply { + ($op:expr, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ + use std::borrow::Borrow; + let op = $op.borrow(); + let incoords = $incoords; + let outcoords = $outcoords; + assert_eq!(incoords.len(), outcoords.len()); + let stride; + let mut work: Vec<_>; + if incoords.len() == 0 { + stride = op.dim_out(); + work = Vec::with_capacity(0); + } else { + stride = outcoords[0].len(); + work = iter::repeat(-1.0).take(outcoords.len() * stride).collect(); + for (work, incoord) in iter::zip(work.chunks_mut(stride), incoords.iter()) { + work[..incoord.len()].copy_from_slice(incoord); + } + } + assert_eq!(op.apply($inidx, &mut work, stride), $outidx); + for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { + assert_abs_diff_eq!(actual[..], desired[..]); + } + }}; + ($op:expr, $inidx:expr, $outidx:expr) => {{ + use std::borrow::Borrow; + let op = $op.borrow(); + let mut work = Vec::with_capacity(0); + assert_eq!(op.apply($inidx, &mut work, op.dim_out()), $outidx); + }}; + } + + #[test] + fn apply_transpose() { + let op = Operator::new_transpose(3, 2); + assert_eq_apply!(op, 0, 0); + assert_eq_apply!(op, 1, 3); + assert_eq_apply!(op, 2, 1); + assert_eq_apply!(op, 3, 4); + assert_eq_apply!(op, 4, 2); + assert_eq_apply!(op, 5, 5); + assert_eq_apply!(op, 6, 6); + assert_eq_apply!(op, 7, 9); + } + + #[test] + fn apply_take() { + let op = Operator::new_take([4, 1, 2], 5); + assert_eq_apply!(op, 0, 4); + assert_eq_apply!(op, 1, 1); + assert_eq_apply!(op, 2, 2); + assert_eq_apply!(op, 3, 9); + assert_eq_apply!(op, 4, 6); + assert_eq_apply!(op, 5, 7); + } + + #[test] + fn apply_children_line() { + let op = Operator::new_children(Line); + assert_eq_apply!(op, 0, [[0.0], [1.0]], 0, [[0.0], [0.5]]); + assert_eq_apply!(op, 1, [[0.0], [1.0]], 0, [[0.5], [1.0]]); + assert_eq_apply!(op, 2, [[0.0], [1.0]], 1, [[0.0], [0.5]]); + + let op = op.with_offset(1); + assert_eq_apply!(op, 3, [[0.2, 0.0], [0.3, 1.0]], 1, [[0.2, 0.5], [0.3, 1.0]]); + } + + #[test] + fn apply_edges_line() { + let op = Operator::new_edges(Line); + assert_eq_apply!(op, 0, [[]], 0, [[1.0]]); + assert_eq_apply!(op, 1, [[]], 0, [[0.0]]); + assert_eq_apply!(op, 2, [[]], 1, [[1.0]]); + + let op = op.with_offset(1); + assert_eq_apply!(op, 0, [[0.2]], 0, [[0.2, 1.0]]); + } + + #[test] + fn apply_uniform_points() { + let op = Operator::new_uniform_points([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); + assert_eq_apply!(op, 0, [[]], 0, [[1.0, 2.0]]); + assert_eq_apply!(op, 1, [[]], 0, [[3.0, 4.0]]); + assert_eq_apply!(op, 2, [[]], 0, [[5.0, 6.0]]); + assert_eq_apply!(op, 3, [[]], 1, [[1.0, 2.0]]); + + let op = op.with_offset(1); + assert_eq_apply!(op, 0, [[7.0]], 0, [[7.0, 1.0, 2.0]]); + } + + macro_rules! assert_unapply { + ($op:expr) => {{ + let op = $op; + let nin = 2 * op.len_in(); + let nout = 2 * op.len_out(); + assert!(nout > 0); + let mut map: Vec> = (0..nout).map(|_| Vec::new()).collect(); + let mut work = Vec::with_capacity(0); + for i in 0..nin { + map[op.apply(i, &mut work, op.offset() + op.dim_out())].push(i); + } + for (j, desired) in map.into_iter().enumerate() { + let mut actual = op.unapply_indices(&[j]); + actual.sort(); + assert_eq!(actual, desired); + } + }}; + } + + #[test] + fn unapply_indices_transpose() { + assert_unapply!(Operator::new_transpose(3, 2)); + } + + #[test] + fn unapply_indices_take() { + assert_unapply!(Operator::new_take([4, 1], 5)); + } + + #[test] + fn unapply_indices_children() { + assert_unapply!(Operator::new_children(Triangle)); + } + + #[test] + fn unapply_indices_edges() { + assert_unapply!(Operator::new_edges(Triangle)); + } + + #[test] + fn unapply_indices_uniform_points() { + assert_unapply!(Operator::new_uniform_points( + [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + 2 + )); + } + + macro_rules! assert_equiv_chains { + ($a:expr, $b:expr $(, $simplex:ident)*) => {{ + let a: &[Operator] = &$a; + let b: &[Operator] = &$b; + println!("a: {a:?}"); + println!("b: {b:?}"); + let tip_dim = 0 $(+ $simplex.dim())*; + // Determine the length of the sequence and the root dimension for sequence `a`. + let mut tip_len = 1; + let mut root_len = 1; + let mut root_dim = tip_dim; + for op in a.iter().rev() { + let i = (1..) + .into_iter() + .find(|i| (root_len * i) % op.len_in() == 0) + .unwrap(); + tip_len *= i; + root_len *= i; + root_len = root_len / op.len_in() * op.len_out(); + assert!(op.offset() + op.dim_in() <= root_dim); + root_dim += op.delta_dim(); + } + assert!(tip_len > 0); + // Verify the length and the root dimension for sequence `b`. + let mut root_len_b = tip_len; + let mut root_dim_b = tip_dim; + for op in b.iter().rev() { + assert_eq!(root_len_b % op.len_in(), 0); + root_len_b = root_len_b / op.len_in() * op.len_out(); + assert!(op.offset() + op.dim_in() <= root_dim_b); + root_dim_b += op.delta_dim(); + } + assert_eq!(root_len_b, root_len); + assert_eq!(root_dim_b, root_dim); + // Build coords: the outer product of the vertices of the given simplices, zero-padded + // to the dimension of the root. + let coords = iter::once([]); + $( + let coords = coords.flat_map(|coord| { + $simplex + .vertices() + .chunks($simplex.dim()) + .map(move |vert| [&coord, vert].concat()) + }); + )* + let pad: Vec = iter::repeat(0.0).take(root_dim - tip_dim).collect(); + let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); + // Test if every tip index and coordinate maps to the same root index and coordinate. + for itip in 0..2 * tip_len { + let mut crds_a = coords.clone(); + let mut crds_b = coords.clone(); + let iroot_a = a.iter().rev().fold(itip, |i, op| op.apply(i, &mut crds_a, root_dim)); + let iroot_b = b.iter().rev().fold(itip, |i, op| op.apply(i, &mut crds_b, root_dim)); + assert_eq!(iroot_a, iroot_b, "itip={itip}"); + assert_abs_diff_eq!(crds_a[..], crds_b[..]); + } + }}; + } + + macro_rules! assert_shift_left { + ($($op:expr),*; $($simplex:ident),*) => {{ + let unshifted = [$(Operator::from($op),)*]; + let (ltrans, lop, lchain) = unshifted.last().unwrap().shift_left(&unshifted[..unshifted.len()-1]).unwrap(); + let mut shifted: Vec = Vec::new(); + if let Some(ltrans) = ltrans { + shifted.push(ltrans.into()); + } + shifted.push(lop); + shifted.extend(lchain.into_iter()); + assert_equiv_chains!(&shifted[..], &unshifted[..] $(, $simplex)*); + }}; + } + + #[test] + fn shift_left() { + assert_shift_left!( + Transpose::new(4, 3), Operator::new_take([0, 1], 3); + ); + assert_shift_left!( + Transpose::new(3, 5), Transpose::new(5, 4*3), Operator::new_take([0, 1], 3); + ); + assert_shift_left!( + Transpose::new(5, 4), Transpose::new(5*4, 3), Operator::new_take([0, 1], 3); + ); + assert_shift_left!( + Operator::new_children(Line).with_offset(1), Operator::new_children(Line); + Line, Line + ); + assert_shift_left!( + Operator::new_edges(Line), Operator::new_children(Line); + Line + ); + assert_shift_left!( + Operator::new_edges(Line).with_offset(1), Operator::new_children(Line); + Line + ); + assert_shift_left!( + Operator::new_edges(Triangle), Operator::new_children(Line); + Line + ); + } +} diff --git a/src/simplex.rs b/src/simplex.rs index 53f4cafab..813e54012 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -1,5 +1,3 @@ -use crate::types::Dim; - /// Simplex. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Simplex { @@ -10,7 +8,7 @@ pub enum Simplex { impl Simplex { /// Returns the dimension of the simplex. #[inline] - pub const fn dim(&self) -> Dim { + pub const fn dim(&self) -> usize { match self { Self::Line => 1, Self::Triangle => 2, @@ -18,7 +16,7 @@ impl Simplex { } /// Returns the dimension of an edge of the simplex. #[inline] - pub const fn edge_dim(&self) -> Dim { + pub const fn edge_dim(&self) -> usize { self.dim() - 1 } /// Returns the number of edges of the simplex. @@ -88,30 +86,42 @@ impl Simplex { } } - /// Transform the given child `coordinate` for child `index` to this parent + /// Transform the given child `coordinates` for child `index` to this parent /// simplex in-place. The returned index is the index of the parent in an /// infinite, uniform sequence. - pub fn apply_child_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { + pub fn apply_child( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { match self { Self::Line => { - coordinate[0] = 0.5 * (coordinate[0] + (index % 2) as f64); + for coordinate in coordinates.chunks_mut(stride) { + let coordinate = &mut coordinate[offset..]; + coordinate[0] = 0.5 * (coordinate[0] + (index % 2) as f64); + } index / 2 } Self::Triangle => { - coordinate[0] *= 0.5; - coordinate[1] *= 0.5; - match index % 4 { - 1 => { - coordinate[0] += 0.5; - } - 2 => { - coordinate[1] += 0.5; + for coordinate in coordinates.chunks_mut(stride) { + let coordinate = &mut coordinate[offset..]; + coordinate[0] *= 0.5; + coordinate[1] *= 0.5; + match index % 4 { + 1 => { + coordinate[0] += 0.5; + } + 2 => { + coordinate[1] += 0.5; + } + 3 => { + coordinate[1] += coordinate[0]; + coordinate[0] = 0.5 - coordinate[0]; + } + _ => {} } - 3 => { - coordinate[1] += coordinate[0]; - coordinate[0] = 0.5 - coordinate[0]; - } - _ => {} } index / 4 } @@ -124,41 +134,55 @@ impl Simplex { } } - pub fn unapply_child_indices(&self, indices: &[usize]) -> Vec { - let mut child_indices = Vec::with_capacity(indices.len() * self.nchildren()); - for index in indices.iter() { - for ichild in 0..self.nchildren() { - child_indices.push(index * self.nchildren() + ichild); - } - } - child_indices + pub fn unapply_child_indices( + &self, + indices: impl Iterator, + ) -> impl Iterator { + let n = self.nchildren(); + indices.flat_map(move |i| (0..n).map(move |j| i * n + j)) } /// Transform the given edge `coordinate` for edge `index` to this parent /// simplex in-place. The returned index is the index of the parent in an /// infinite, uniform sequence. - pub fn apply_edge_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { - coordinate.copy_within( - self.edge_dim() as usize..coordinate.len() - 1, - self.dim() as usize, - ); + pub fn apply_edge( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { match self { Self::Line => { - coordinate[0] = (1 - index % 2) as f64; + for coordinate in coordinates.chunks_mut(stride) { + let coordinate = &mut coordinate[offset..]; + coordinate.copy_within( + self.edge_dim() as usize..coordinate.len() - 1, + self.dim() as usize, + ); + coordinate[0] = (1 - index % 2) as f64; + } index / 2 } Self::Triangle => { - match index % 3 { - 0 => { - coordinate[1] = coordinate[0]; - coordinate[0] = 1.0 - coordinate[0]; - } - 1 => { - coordinate[1] = coordinate[0]; - coordinate[0] = 0.0; - } - _ => { - coordinate[1] = 0.0; + for coordinate in coordinates.chunks_mut(stride) { + let coordinate = &mut coordinate[offset..]; + coordinate.copy_within( + self.edge_dim() as usize..coordinate.len() - 1, + self.dim() as usize, + ); + match index % 3 { + 0 => { + coordinate[1] = coordinate[0]; + coordinate[0] = 1.0 - coordinate[0]; + } + 1 => { + coordinate[1] = coordinate[0]; + coordinate[0] = 0.0; + } + _ => { + coordinate[1] = 0.0; + } } } index / 3 @@ -172,14 +196,12 @@ impl Simplex { } } - pub fn unapply_edge_indices(&self, indices: &[usize]) -> Vec { - let mut edge_indices = Vec::with_capacity(indices.len() * self.nedges()); - for index in indices.iter() { - for iedge in 0..self.nedges() { - edge_indices.push(index * self.nedges() + iedge); - } - } - edge_indices + pub fn unapply_edge_indices( + &self, + indices: impl Iterator, + ) -> impl Iterator { + let n = self.nedges(); + indices.flat_map(move |i| (0..n).map(move |j| i * n + j)) } } @@ -187,22 +209,21 @@ impl Simplex { mod tests { use super::*; use approx::assert_abs_diff_eq; + use std::iter; use Simplex::*; macro_rules! assert_child_index_coord { ($simplex:ident, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ let incoords = $incoords; let outcoords = $outcoords; - for i in 0..outcoords.len() { - let mut work = outcoords[i].clone(); - for j in 0..incoords[i].len() { - work[j] = incoords[i][j]; - } - for j in incoords[i].len()..outcoords[i].len() { - work[j] = -1.0; - } - assert_eq!($simplex.apply_child_inplace($inidx, &mut work), $outidx); - assert_abs_diff_eq!(work[..], outcoords[i][..]); + let stride = outcoords[0].len(); + let mut work: Vec<_> = iter::repeat(-1.0).take(outcoords.len() * stride).collect(); + for (work, incoord) in iter::zip(work.chunks_mut(stride), incoords.iter()) { + work[..incoord.len()].copy_from_slice(incoord); + } + assert_eq!($simplex.apply_child($inidx, &mut work, stride, 0), $outidx); + for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { + assert_abs_diff_eq!(actual[..], desired[..]); } }}; } @@ -211,16 +232,14 @@ mod tests { ($simplex:ident, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ let incoords = $incoords; let outcoords = $outcoords; - for i in 0..outcoords.len() { - let mut work = outcoords[i].clone(); - for j in 0..incoords[i].len() { - work[j] = incoords[i][j]; - } - for j in incoords[i].len()..outcoords[i].len() { - work[j] = -1.0; - } - assert_eq!($simplex.apply_edge_inplace($inidx, &mut work), $outidx); - assert_abs_diff_eq!(work[..], outcoords[i][..]); + let stride = outcoords[0].len(); + let mut work: Vec<_> = iter::repeat(-1.0).take(outcoords.len() * stride).collect(); + for (work, incoord) in iter::zip(work.chunks_mut(stride), incoords.iter()) { + work[..incoord.len()].copy_from_slice(incoord); + } + assert_eq!($simplex.apply_edge($inidx, &mut work, stride, 0), $outidx); + for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { + assert_abs_diff_eq!(actual[..], desired[..]); } }}; } @@ -281,8 +300,8 @@ mod tests { for (i, j) in Line.swap_edges_children_map().iter().cloned().enumerate() { let mut x = [0.5]; let mut y = [0.5]; - Line.apply_edge_inplace(i, &mut x); - Line.apply_child_inplace(Line.apply_edge_inplace(j, &mut y), &mut y); + Line.apply_edge(i, &mut x, 1, 0); + Line.apply_child(Line.apply_edge(j, &mut y, 1, 0), &mut y, 1, 0); assert_abs_diff_eq!(x[..], y[..]); } } @@ -297,8 +316,8 @@ mod tests { { let mut x = [0.5, 0.5]; let mut y = [0.5, 0.5]; - Triangle.apply_edge_inplace(Line.apply_child_inplace(i, &mut x), &mut x); - Triangle.apply_child_inplace(Triangle.apply_edge_inplace(j, &mut y), &mut y); + Triangle.apply_edge(Line.apply_child(i, &mut x, 2, 0), &mut x, 2, 0); + Triangle.apply_child(Triangle.apply_edge(j, &mut y, 2, 0), &mut y, 2, 0); assert_abs_diff_eq!(x[..], y[..]); } } @@ -309,8 +328,8 @@ mod tests { if let Some(j) = j { let mut x = [0.5]; let mut y = [0.5]; - Line.apply_child_inplace(Line.apply_edge_inplace(i, &mut x), &mut x); - Line.apply_child_inplace(Line.apply_edge_inplace(j, &mut y), &mut y); + Line.apply_child(Line.apply_edge(i, &mut x, 1, 0), &mut x, 1, 0); + Line.apply_child(Line.apply_edge(j, &mut y, 1, 0), &mut y, 1, 0); assert_abs_diff_eq!(x[..], y[..]); } } @@ -322,8 +341,8 @@ mod tests { if let Some(j) = j { let mut x = [0.5, 0.5]; let mut y = [0.5, 0.5]; - Triangle.apply_child_inplace(Triangle.apply_edge_inplace(i, &mut x), &mut x); - Triangle.apply_child_inplace(Triangle.apply_edge_inplace(j, &mut y), &mut y); + Triangle.apply_child(Triangle.apply_edge(i, &mut x, 2, 0), &mut x, 2, 0); + Triangle.apply_child(Triangle.apply_edge(j, &mut y, 2, 0), &mut y, 2, 0); assert_abs_diff_eq!(x[..], y[..]); } } diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index a1ca39912..000000000 --- a/src/types.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub type Dim = u8; - From fc00d1062940670550cde89e17d313c47acbe9e0 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Fri, 17 Jun 2022 00:15:39 +0200 Subject: [PATCH 10/45] WIP --- src/chain.rs | 901 ++++++++++-------------------------------------- src/lib.rs | 79 ++++- src/operator.rs | 583 +++++++++++++++++-------------- src/sequence.rs | 79 ++--- 4 files changed, 607 insertions(+), 1035 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index 7319e109f..e015ba865 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,702 +1,158 @@ -//pub(crate) trait UnsizedSequence: Clone { -// /// Returns the difference between the dimension of the input and the output sequence. -// fn delta_dim(&self) -> usize; -// fn delta_len(&self) -> (usize, usize); -// /// Map the index. -// fn apply_index(&self, index: usize) -> usize; -// /// Returns all indices that map to the given indices. TODO: examples with take and children? -// fn unapply_indices(&self, indices: &[usize]) -> Vec; -// /// Map the index and coordinate of an element in the output sequence to -// /// the input sequence. The index is returned, the coordinate is adjusted -// /// in-place. If the coordinate dimension of the input sequence is larger -// /// than that of the output sequence, the [Operator::delta_dim()] last -// /// elements of the coordinate are discarded. -// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize; -// /// Map the index and multiple coordinates of an element in the output -// /// sequence to the input sequence. The index is returned, the coordinates -// /// are adjusted in-place. -// fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize, stride: usize) -> usize { -// let dim = dim as usize; -// let mut result_index = 0; -// for i in 0..coordinates.len() / stride { -// result_index = self.apply_one_inplace(index, &mut coordinates[i * dim..(i + 1) * dim]); -// } -// result_index -// } -// /// Map the index and coordinate of an element in the output sequence to -// /// the input sequence. -// fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec) { -// let delta_dim = self.delta_dim() as usize; -// let to_dim = coordinate.len() + delta_dim; -// let mut result = Vec::with_capacity(to_dim); -// result.extend_from_slice(coordinate); -// result.extend(iter::repeat(0.0).take(delta_dim)); -// (self.apply_one_inplace(index, &mut result), result) -// } -// /// Map the index and multiple coordinates of an element in the output -// /// sequence to the input sequence. -// fn apply_many(&self, index: usize, coordinates: &[f64], dim: usize) -> (usize, Vec) { -// assert_eq!(coordinates.len() % dim as usize, 0); -// let ncoords = coordinates.len() / dim as usize; -// let delta_dim = self.delta_dim(); -// let to_dim = dim + delta_dim; -// let mut result = Vec::with_capacity(ncoords * to_dim as usize); -// for coord in coordinates.chunks(dim as usize) { -// result.extend_from_slice(coord); -// result.extend(iter::repeat(0.0).take(delta_dim as usize)); -// } -// (self.apply_many_inplace(index, &mut result, to_dim), result) -// } -//} -// -//macro_rules! dispatch { -// ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { -// #[inline] -// $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { -// match $self { -// Operator::Take(var) => var.$fn($($arg),*), -// Operator::Children(var) => var.$fn($($arg),*), -// Operator::Edges(var) => var.$fn($($arg),*), -// Operator::UniformPoints(var) => var.$fn($($arg),*), -// } -// } -// }; -// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { -// #[inline] -// $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { -// match $self { -// Operator::Take(var) => var.$fn($($arg),*), -// Operator::Children(var) => var.$fn($($arg),*), -// Operator::Edges(var) => var.$fn($($arg),*), -// Operator::UniformPoints(var) => var.$fn($($arg),*), -// } -// } -// }; -//} -// -//impl UnsizedSequence for Operator { -// dispatch! {fn delta_dim(&self) -> usize} -// dispatch! {fn delta_len(&self) -> (usize, usize)} -// dispatch! {fn apply_index(&self, index: usize) -> usize} -// dispatch! {fn unapply_indices(&self, indices: &[usize]) -> Vec} -// dispatch! {fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize} -// dispatch! {fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize) -> usize} -// dispatch! {fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec)} -// dispatch! {fn apply_many(&self, index: usize, coordinates: &[f64], dim: usize) -> (usize, Vec)} -//} -// -//impl WithStride for Operator { -// dispatch! {fn stride(&mut self) -> usize} -// dispatch! {fn increment_stride(&mut self, amount: usize)} -// dispatch! {fn decrement_stride(&mut self, amount: usize)} -//} -// -//impl WithOffset for Operator { -// dispatch! {fn offset(&mut self) -> usize} -// dispatch! {fn increment_offset(&mut self, amount: usize)} -// dispatch! {fn decrement_offset(&mut self, amount: usize)} -//} -// -// -//trait IndexOperator { -// /// Returns the difference between the dimension of the input and the output sequence. -// fn delta_dim(&self) -> usize; -// fn delta_len(&self) -> (usize, usize); -// /// Map the index. -// fn apply_index(&self, index: usize) -> usize; -// /// Returns all indices that map to the given indices. TODO: examples with take and children? -// fn unapply_indices(&self, indices: &[usize]) -> Vec; -// /// Map the index and coordinate of an element in the output sequence to -// /// the input sequence. The index is returned, the coordinate is adjusted -// /// in-place. If the coordinate dimension of the input sequence is larger -// /// than that of the output sequence, the [Operator::delta_dim()] last -// /// elements of the coordinate are discarded. -//} -// -//impl UnsizedSequence for IndexOperator { -// fn delta_dim(&self) -> usize { -// self.delta_dim() -// } -// fn delta_len(&self) -> (usize, usize) { -// self.delta_len() -// } -// fn apply_index(&self, index: usize) -> usize { -// self.apply_index(index) -// } -// fn unapply_indices(&self, indices: &[usize]) -> Vec { -// self.unapply_indices(indices) -// } -// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { -// self.apply_index(index) -// } -// fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize, stride: usize) -> usize { -// self.apply_index(index) -// } -//} -// -//impl WithOffset for IndexOperator { -// fn offset(&self) -> usize { -// 0 -// } -// fn increment_offset(&self, _amount: usize) {} -// fn decrement_offset(&self, _amount: usize) {} -//} -// -// -// -// -// -//impl DescribeOperator for Operator { -// dispatch! {fn operator_kind(&self) -> OperatorKind} -// dispatch! {fn as_children(&self) -> Option<&Children>} -// dispatch! {fn as_children_mut(&mut self) -> Option<&mut Children>} -// dispatch! {fn as_edges(&self) -> Option<&Edges>} -// dispatch! {fn as_edges_mut(&mut self) -> Option<&mut Edges>} -//} -// -//impl std::fmt::Debug for Operator { -// dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result} -//} -// -//#[derive(Debug, Clone, PartialEq)] -//enum OperatorKind { -// Index(usize, usize), -// Coordinate(usize, usize, usize, usize), -// Other, -//} -// -//trait DescribeOperator { -// /// Returns the kind of operation the operator applies to its parent sequence. -// fn operator_kind(&self) -> OperatorKind; -// #[inline] -// fn as_children(&self) -> Option<&Children> { -// None -// } -// #[inline] -// fn as_children_mut(&mut self) -> Option<&mut Children> { -// None -// } -// #[inline] -// fn as_edges(&self) -> Option<&Edges> { -// None -// } -// #[inline] -// fn as_edges_mut(&mut self) -> Option<&mut Edges> { -// None -// } -//} -// -//// TODO: add StrictMonotonicIncreasingTake -// -//#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -//pub struct Take { -// indices: Box<[usize]>, -// len: usize, -// stride: usize, -//} -// -//impl Take { -// #[inline] -// pub fn new(indices: impl Into>, len: usize) -> Self { -// Self { -// indices: indices.into(), -// len, -// stride: 1, -// } -// } -//} -// -//#[inline] -//fn divmod(x: usize, y: usize) -> (usize, usize) { -// (x / y, x % y) -//} -// -//// macro_rules! unflatten_index { -//// ($rem:expr $(, $index:ident)*; $($len:expr,)* $len:expr) => {{ -//// let (rem, index) = divmod($rem, $len); -//// unflatten_index!($rem, index $(, $index)*; $($len),*) -//// }}; -//// } -//// -//// macro_rules! flatten_indices { -//// ($flat:expr;) => {$flat}; -//// ($flat:expr, $index:expr $(, $index:expr)*; $len:expr $(, $len:expr)*) => { -//// flatten_indices!(($flat + $index) * $len $(, $index)*; $($len),*) -//// } -//// } -// -//trait LenInOut { -// fn len_in(&self) -> usize; -// fn len_out(&self) -> usize; -//} -// -//impl LenInOut for usize { -// fn len_in(&self) -> { -// *self -// } -// fn len_out(&self) -> { -// *self -// } -//} -// -//impl LenInOut for [usize; 2] { -// fn len_in(&self) -> { -// *self[0] -// } -// fn len_out(&self) -> { -// *self[1] -// } -//} -// -//#[inline] -//fn map_sandwiched_index(index: usize, len0: impl LenInOut, len1: impl LenInOut, f: impl FnOnce(usize) -> usize) -> usize { -// let (index, i2 = divmod(index, len1.len_in()); -// let (i0, i1) = divmod(index, len0.len_in()); -// (i0 * len0.len_out() + f(i1)) * len1.len_out() + i2 -//} -// -//#[inline] -//fn map_strided_index(index: usize, stride: usize, f: impl FnOnce(usize) -> usize) -> usize { -// f(index / stride) * stride + (index % stride) -//} -// -//impl UnsizedSequence for Take { -// #[inline] -// fn delta_dim(&self) -> usize { -// 0 -// } -// #[inline] -// fn delta_len(&self) -> (usize, usize) { -// (self.indices.len(), self.len) -// } -// #[inline] -// fn increment_offset(&mut self, _amount: usize) {} -// #[inline] -// fn decrement_offset(&mut self, _amount: usize) {} -// #[inline] -// fn increment_stride(&mut self, amount: usize) { -// self.stride *= amount; -// } -// #[inline] -// fn decrement_stride(&mut self, amount: usize) { -// self.stride /= amount; -// } -// #[inline] -// fn apply_index(&self, index: usize) -> usize { -// map_sandwiched_index(index, [self.indices.len(), self.len], self.stride, |i| self.indices[i]) -// } -// #[inline] -// fn unapply_indices(&self, indices: &[usize]) -> Vec { -// indices.iter().filter_map(|index| { -// let (i0, i1, i2) = unflatten_index!(index; self.len, self.stride); -// self.indices.iter().position(|i| *i == i1).map(|j1| flatten_indices!(i0, j1, i2; self.indices.len(), self.stride)) -// }).collect() -// } -// #[inline] -// fn apply_one_inplace(&self, index: usize, _coordinate: &mut [f64]) -> usize { -// self.apply_index(index) -// } -// #[inline] -// fn apply_many_inplace(&self, index: usize, _coordinate: &mut [f64], _dim: usize) -> usize { -// self.apply_index(index) -// } -//} -// -//impl DescribeOperator for Take { -// #[inline] -// fn operator_kind(&self) -> OperatorKind { -// OperatorKind::Index(self.len, self.indices.len()) -// } -//} -// -//#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -//pub struct Children { -// simplex: Simplex, -// offset: usize, -// stride: usize, -//} -// -//impl Children { -// #[inline] -// pub fn new(simplex: Simplex, offset: usize) -> Self { -// Self { simplex, offset, stride: 1 } -// } -//} -// -//impl UnsizedSequence for Children { -// #[inline] -// fn delta_dim(&self) -> usize { -// 0 -// } -// #[inline] -// fn delta_len(&self) -> (usize, usize) { -// (self.simplex.nchildren(), 1) -// } -// #[inline] -// fn increment_offset(&mut self, amount: usize) { -// self.offset += amount; -// } -// #[inline] -// fn decrement_offset(&mut self, amount: usize) { -// self.offset -= amount; -// } -// #[inline] -// fn increment_stride(&mut self, amount: usize) { -// self.stride *= amount; -// } -// #[inline] -// fn decrement_stride(&mut self, amount: usize) { -// self.stride /= amount; -// } -// #[inline] -// fn apply_index(&self, index: usize) -> usize { -// map_strided_index(index, self.stride, |i| self.simplex.apply_child_index(i)) -// } -// #[inline] -// fn unapply_indices(&self, indices: &[usize]) -> Vec { -// self.simplex.unapply_child_indices(indices) -// } -// #[inline] -// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { -// self.simplex -// .apply_child(index, &mut coordinate[self.offset as usize..]) -// } -//} -// -//impl DescribeOperator for Children { -// #[inline] -// fn operator_kind(&self) -> OperatorKind { -// OperatorKind::Coordinate( -// self.offset, -// self.simplex.dim(), -// self.simplex.dim(), -// self.simplex.nchildren(), -// ) -// } -// #[inline] -// fn as_children(&self) -> Option<&Children> { -// Some(self) -// } -// #[inline] -// fn as_children_mut(&mut self) -> Option<&mut Children> { -// Some(self) -// } -//} -// -//#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -//pub struct Edges { -// simplex: Simplex, -// offset: usize, -//} -// -//impl Edges { -// #[inline] -// pub fn new(simplex: Simplex, offset: usize) -> Self { -// Self { simplex, offset } -// } -//} -// -//impl UnsizedSequence for Edges { -// #[inline] -// fn delta_dim(&self) -> usize { -// 1 -// } -// #[inline] -// fn delta_len(&self) -> (usize, usize) { -// (self.simplex.nedges(), 1) -// } -// #[inline] -// fn increment_offset(&mut self, amount: usize) { -// self.offset += amount; -// } -// #[inline] -// fn decrement_offset(&mut self, amount: usize) { -// self.offset -= amount; -// } -// #[inline] -// fn increment_stride(&mut self, amount: usize) { -// self.stride *= amount; -// } -// #[inline] -// fn decrement_stride(&mut self, amount: usize) { -// self.stride /= amount; -// } -// #[inline] -// fn apply_index(&self, index: usize) -> usize { -// let i0, i1 = unflatten_index!(index; self.stride); -// flatten_indices!(self.simplex.apply_edge_index(i0), i1; self.stride) -// } -// #[inline] -// fn unapply_indices(&self, indices: &[usize]) -> Vec { -// self.simplex.unapply_edge_indices(indices) -// } -// #[inline] -// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { -// self.simplex.apply_edge(index, &mut coordinate[self.offset as usize..]) -// } -//} -// -//impl DescribeOperator for Edges { -// #[inline] -// fn operator_kind(&self) -> OperatorKind { -// OperatorKind::Coordinate( -// self.offset, -// self.simplex.dim(), -// self.simplex.edge_dim(), -// self.simplex.nedges(), -// ) -// } -// #[inline] -// fn as_edges(&self) -> Option<&Edges> { -// Some(self) -// } -// #[inline] -// fn as_edges_mut(&mut self) -> Option<&mut Edges> { -// Some(self) -// } -//} -// -//#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -//pub struct UniformPoints { -// points: Box<[FiniteF64]>, -// point_dim: usize, -// offset: usize, -//} -// -//impl UniformPoints { -// pub fn new(points: Box<[f64]>, point_dim: usize, offset: usize) -> Self { -// // TODO: assert that the points are actually finite. -// let points: Box<[FiniteF64]> = unsafe { std::mem::transmute(points) }; -// Self { -// points, -// point_dim, -// offset, -// } -// } -// #[inline] -// pub const fn npoints(&self) -> usize { -// self.points.len() / self.point_dim as usize -// } -//} -// -//impl UnsizedSequence for UniformPoints { -// #[inline] -// fn delta_dim(&self) -> usize { -// self.point_dim -// } -// #[inline] -// fn delta_len(&self) -> (usize, usize) { -// (self.npoints(), 1) -// } -// #[inline] -// fn increment_offset(&mut self, amount: usize) { -// self.offset += amount; -// } -// #[inline] -// fn decrement_offset(&mut self, amount: usize) { -// self.offset -= amount; -// } -// #[inline] -// fn increment_stride(&mut self, amount: usize) { -// self.stride *= amount; -// } -// #[inline] -// fn decrement_stride(&mut self, amount: usize) { -// self.stride /= amount; -// } -// #[inline] -// fn apply_index(&self, index: usize) -> usize { -// index / self.npoints() -// } -// #[inline] -// fn unapply_indices(&self, indices: &[usize]) -> Vec { -// let mut point_indices = Vec::with_capacity(indices.len() * self.npoints()); -// for index in indices.iter() { -// for ipoint in 0..self.npoints() { -// point_indices.push(index * self.npoints() + ipoint); -// } -// } -// point_indices -// } -// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { -// let point_dim = self.point_dim as usize; -// let coordinate = &mut coordinate[self.offset as usize..]; -// coordinate.copy_within(..coordinate.len() - point_dim, point_dim); -// let ipoint = index % self.npoints(); -// let offset = ipoint as usize * point_dim; -// let points: &[f64] = -// unsafe { std::mem::transmute(&self.points[offset..offset + point_dim]) }; -// coordinate[..point_dim].copy_from_slice(points); -// index / self.npoints() -// } -//} -// -//impl DescribeOperator for UniformPoints { -// #[inline] -// fn operator_kind(&self) -> OperatorKind { -// OperatorKind::Coordinate(self.offset, self.point_dim, 0, self.npoints()) -// } -//} -// -///// An operator that maps a sequence of elements to another sequence of elements. -///// -///// Given a sequence of elements an [`Operator`] defines a new sequence. For -///// example [`Operator::Children`] gives the sequence of child elements and -///// [`Operator::Take`] gives a subset of the input sequence. -///// -///// All variants of [`Operator`] apply some operation to either every element of -///// the parent sequence, variants [`Operator::Children`], [`Operator::Edges`] -///// and [`Operator::UniformPoints`], or to consecutive chunks of the input -///// sequence, in which case the size of the chunks is included in the variant -///// and the input sequence is assumed to be a multiple of the chunk size long. -//#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -//pub enum Operator { -// Transpose(Transpose), -// /// A subset of a sequence: the input sequence is reshaped to `(_, len)`, -// /// the given `indices` are taken from the last axis and the result is -// /// flattened. -// Take(Take), -// /// The children of a every element of a sequence. -// Children(Children), -// /// The edges of a every element of a sequence. -// Edges(Edges), -// UniformPoints(UniformPoints), -//} -// -//macro_rules! impl_from_for_operator { -// ($($Variant:ident),*) => {$( -// impl From<$Variant> for Operator { -// fn from(variant: $Variant) -> Self { -// Self::$Variant(variant) -// } -// } -// )*} -//} -// -//impl_from_for_operator! {Transpose, Take, Children, Edges, UniformPoints} -// -//impl Operator { -// /// Construct a new operator that transposes a sequence of elements. -// pub fn new_transpose(len1: usize, len2: usize) -> Self { -// Transpose::new(len1, len2).into() -// } -// /// Construct a new operator that takes a subset of a sequence of elements. -// pub fn new_take(indices: impl Into>, len: usize) -> Self { -// Take::new(indices, len).into() -// } -// /// Construct a new operator that maps a sequence of elements to its children. -// pub fn new_children(simplex: Simplex, offset: usize) -> Self { -// Children::new(simplex, offset).into() -// } -// /// Construct a new operator that maps a sequence of elements to its edges. -// pub fn new_edges(simplex: Simplex, offset: usize) -> Self { -// Edges::new(simplex, offset).into() -// } -// /// Construct a new operator that adds points to every element of a sequence. -// pub fn new_uniform_points(points: Box<[f64]>, point_dim: usize, offset: usize) -> Self { -// UniformPoints::new(points, point_dim, offset).into() -// } -// pub fn swap(&self, other: &Self) -> Option> { -// let mut other = other.clone(); -// swap(self, &mut other).map(|tail| iter::once(other).chain(tail.into_iter()).collect()) -// } -//} -// -//macro_rules! dispatch { -// ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { -// #[inline] -// $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { -// match $self { -// Operator::Transpose(var) => var.$fn($($arg),*), -// Operator::Take(var) => var.$fn($($arg),*), -// Operator::Children(var) => var.$fn($($arg),*), -// Operator::Edges(var) => var.$fn($($arg),*), -// Operator::UniformPoints(var) => var.$fn($($arg),*), -// } -// } -// }; -// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { -// #[inline] -// $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { -// match $self { -// Operator::Transpose(var) => var.$fn($($arg),*), -// Operator::Take(var) => var.$fn($($arg),*), -// Operator::Children(var) => var.$fn($($arg),*), -// Operator::Edges(var) => var.$fn($($arg),*), -// Operator::UniformPoints(var) => var.$fn($($arg),*), -// } -// } -// }; -//} -// -//impl UnsizedSequence for Operator { -// dispatch! {fn delta_dim(&self) -> usize} -// dispatch! {fn delta_len(&self) -> (usize, usize)} -// dispatch! {fn increment_offset(&mut self, amount: usize)} -// dispatch! {fn decrement_offset(&mut self, amount: usize)} -// dispatch! {fn increment_stride(&mut self, amount: usize)} -// dispatch! {fn decrement_stride(&mut self, amount: usize)} -// dispatch! {fn apply_index(&self, index: usize) -> usize} -// dispatch! {fn unapply_indices(&self, indices: &[usize]) -> Vec} -// dispatch! {fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize} -// dispatch! {fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize) -> usize} -// dispatch! {fn apply_one(&self, index: usize, coordinate: &[f64]) -> (usize, Vec)} -// dispatch! {fn apply_many(&self, index: usize, coordinates: &[f64], dim: usize) -> (usize, Vec)} -//} -// -//impl DescribeOperator for Operator { -// dispatch! {fn operator_kind(&self) -> OperatorKind} -// dispatch! {fn as_children(&self) -> Option<&Children>} -// dispatch! {fn as_children_mut(&mut self) -> Option<&mut Children>} -// dispatch! {fn as_edges(&self) -> Option<&Edges>} -// dispatch! {fn as_edges_mut(&mut self) -> Option<&mut Edges>} -//} -// -//impl std::fmt::Debug for Operator { -// dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result} -//} -// -//fn swap(l: &L, r: &mut R) -> Option> -//where -// L: DescribeOperator + UnsizedSequence + Into, -// R: DescribeOperator + UnsizedSequence, -//{ -// if let (Some(edges), Some(children)) = (l.as_edges(), r.as_children_mut()) { -// if edges.offset == children.offset && edges.simplex.edge_dim() == children.simplex.dim() { -// let simplex = edges.simplex; -// let indices = simplex.swap_edges_children_map(); -// let take = Operator::new_take(indices, simplex.nchildren() * simplex.nedges()); -// children.simplex = simplex; -// return Some(vec![l.clone().into(), take]); -// } -// } -// use OperatorKind::*; -// match (l.operator_kind(), r.operator_kind()) { -// (Index(1, 1), Coordinate(_, _, _, _)) => Some(vec![l.clone().into()]), -// (Index(l_nout, l_nin), Coordinate(_, _, _, r_gen)) => Some(vec![ -// Operator::new_transpose(r_gen, l_nout), -// l.clone().into(), -// Operator::new_transpose(l_nin, r_gen), -// ]), -// (Coordinate(l_off, _, l_nin, l_gen), Coordinate(r_off, r_nout, _, r_gen)) => { -// if l_off + l_nin <= r_off { -// r.increment_offset(l.delta_dim()); -// Some(vec![ -// l.clone().into(), -// Operator::new_transpose(l_gen, r_gen), -// ]) -// } else if l_off >= r_off + r_nout { -// let mut l = l.clone(); -// l.decrement_offset(r.delta_dim()); -// Some(vec![l.into(), Operator::new_transpose(l_gen, r_gen)]) -// } else { -// None -// } -// } -// _ => None, -// } -//} -// +use crate::operator::{Operator, Transpose, Edges}; +use crate::simplex::Simplex; +use std::collections::BTreeMap; +use crate::UnsizedMapping; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UnsizedChain(Vec); + +impl UnsizedChain { + #[inline] + pub fn new(operators: Vec) -> Self { + Self(operators) + } + #[inline] + pub fn empty() -> Self { + UnsizedChain(Vec::new()) + } + pub fn push(&mut self, operator: impl Into) { + self.0.push(operator.into()) + } + fn split_heads(&self) -> BTreeMap, Vec)> { + let mut heads = BTreeMap::new(); + for (i, op) in self.0.iter().enumerate() { + if let Some((transpose, head, mut tail)) = op.shift_left(&self.0[..i]) { + tail.extend(self.0[i + 1..].iter().cloned()); + heads.insert(head, (transpose, tail)); + } + if let Operator::Edges(Edges(Simplex::Line, offset)) = op { + let children = Operator::new_children(Simplex::Line).with_offset(*offset); + if let Some((transpose, head, mut tail)) = children.shift_left(&self.0[..i]) { + tail.push(op.clone()); + tail.push(Operator::new_take( + Simplex::Line.swap_edges_children_map(), + Simplex::Line.nedges() * Simplex::Line.nchildren(), + )); + tail.extend(self.0[i + 1..].iter().cloned()); + heads.insert(head, (transpose, tail)); + } + } + } + heads + } + /// Remove and return the common prefix of two chains, transforming either if necessary. + pub fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { + let mut common = Vec::new(); + let mut tail1 = self.clone(); + let mut tail2 = other.clone(); + while !tail1.0.is_empty() && !tail2.0.is_empty() { + if tail1.0.last() == tail2.0.last() { + common.push(tail1.0.pop().unwrap()); + tail2.0.pop(); + continue; + } + let heads1: BTreeMap> = tail1 + .split_heads() + .into_iter() + .filter_map(|(head, (trans, tail))| { + if trans.is_none() { + Some((head, tail)) + } else { + None + } + }) + .collect(); + let mut heads2: BTreeMap> = tail2 + .split_heads() + .into_iter() + .filter_map(|(head, (trans, tail))| { + if trans.is_none() { + Some((head, tail)) + } else { + None + } + }) + .collect(); + if let Some((head, new_tail1, new_tail2)) = heads1 + .into_iter() + .filter_map(|(h, t1)| heads2.remove(&h).map(|t2| (h, t1, t2))) + .min_by_key(|(_, t1, t2)| std::cmp::max(t1.len(), t2.len())) + { + common.push(head); + tail1.0 = new_tail1; + tail2.0 = new_tail2; + continue; + } + break; + } + let common = if tail1.0.is_empty() && (!tail2.0.is_empty() || self.0.len() <= other.0.len()) { + self.clone() + } else if tail2.0.is_empty() { + other.clone() + } else { + Self::new(common) + }; + (common, tail1, tail2) + } +} + +impl UnsizedMapping for UnsizedChain { + fn dim_in(&self) -> usize { + self.0.iter().map(|op| op.dim_in()).sum() + } + fn delta_dim(&self) -> usize { + self.0.iter().map(|op| op.delta_dim()).sum() + } + fn add_offset(&mut self, offset: usize) { + for op in self.0.iter_mut() { + op.add_offset(offset); + } + } + fn mod_out(&self) -> usize { + self.0.iter().map(|op| op.mod_out()).product() + } + fn mod_in(&self) -> usize { + self.0.iter().map(|op| op.mod_in()).product() + } + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { + self.0 + .iter() + .rev() + .fold(index, |index, op| op.apply_inplace(index, coordinates, stride)) + } + fn apply_index(&self, index: usize) -> usize { + self.0.iter().rev().fold(index, |index, op| op.apply_index(index)) + } + fn apply_indices_inplace(&self, indices: &mut [usize]) { + for op in self.0.iter().rev() { + op.apply_indices_inplace(indices); + } + } + fn unapply_indices(&self, indices: &[usize]) -> Vec { + self.0 + .iter() + .fold(indices.to_vec(), |indices, op| op.unapply_indices(&indices)) + } +} + +impl FromIterator for UnsizedChain { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + Self::new(iter.into_iter().collect()) + } +} + +impl IntoIterator for UnsizedChain { + type Item = Operator; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + ///// A chain of [`Operator`]s. //#[derive(Debug, Clone, PartialEq)] //pub struct UnsizedChain { @@ -1282,27 +738,26 @@ // } // } // -// #[test] -// fn remove_common_prefix() { -// let a = UnsizedChain::new([ -// Operator::new_children(Line, 0), -// Operator::new_children(Line, 0), -// ]); -// let b = UnsizedChain::new([Operator::new_edges(Line, 0)]); -// assert_eq!( -// a.remove_common_prefix(&b), -// ( -// UnsizedChain::new([ -// Operator::new_children(Line, 0), -// Operator::new_children(Line, 0) -// ]), -// UnsizedChain::new([]), -// UnsizedChain::new([ -// Operator::new_edges(Line, 0), -// Operator::new_take([2, 1], 4), -// Operator::new_take([2, 1], 4) -// ]), -// ) -// ); -// } -//} + +#[cfg(test)] +mod tests { + use super::*; + use crate::simplex::Simplex::*; + + #[test] + fn remove_common_prefix() { + let c1 = Operator::new_children(Line); + let e1 = Operator::new_edges(Line); + let swap_ec1 = Operator::new_take([2, 1], 4); + let a = UnsizedChain::new(vec![c1.clone(), c1.clone()]); + let b = UnsizedChain::new(vec![e1.clone()]); + assert_eq!( + a.remove_common_prefix(&b), + ( + UnsizedChain::new(vec![c1.clone(), c1.clone()]), + UnsizedChain::empty(), + UnsizedChain::new(vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()]), + ) + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index aea9a783b..921d06e00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,4 +2,81 @@ pub mod chain; mod finite_f64; pub mod operator; pub mod simplex; -//pub mod sequence; +pub mod sequence; + +pub trait Sequence { + fn len(&self) -> usize; + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn root_len(&self) -> usize; + fn dim(&self) -> usize; + fn root_dim(&self) -> usize; + fn delta_dim(&self) -> usize { + self.root_dim() - self.dim() + } + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize; + fn apply_inplace(&self, index: usize, coordinates: &mut[f64]) -> Option { + if index < self.len() { + Some(self.apply_inplace_unchecked(index, coordinates)) + } else { + None + } + } + fn apply_index_unchecked(&self, index: usize) -> usize; + fn apply_index(&self, index: usize) -> Option { + if index < self.len() { + Some(self.apply_index_unchecked(index)) + } else { + None + } + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]); + fn apply_indices(&self, indices: &[usize]) -> Option> { + if indices.iter().all(|index| *index < self.len()) { + let mut indices = indices.to_vec(); + self.apply_indices_inplace_unchecked(&mut indices); + Some(indices) + } else { + None + } + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec; + fn unapply_indices(&self, indices: &[usize]) -> Option> { + if indices.iter().all(|index| *index < self.len()) { + Some(self.unapply_indices_unchecked(indices)) + } else { + None + } + } +} + +pub trait UnsizedMapping { + // Minimum dimension of the input coordinate. If the dimension of the input + // coordinate of [UnsizedMapping::apply()] is larger than the minimum, then + // the mapping of the surplus is the identity mapping. + fn dim_in(&self) -> usize; + // Minimum dimension of the output coordinate. + fn dim_out(&self) -> usize { + self.dim_in() + self.delta_dim() + } + // Difference in dimension of the output and input coordinate. + fn delta_dim(&self) -> usize; + fn add_offset(&mut self, offset: usize); + // Modulus of the input index. The mapping repeats itself at index `mod_in` + // and the output index is incremented with `in_index / mod_in * mod_out`. + fn mod_in(&self) -> usize; + // Modulus if the output index. + fn mod_out(&self) -> usize; + fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize; + fn apply_index(&self, index: usize) -> usize; + fn apply_indices_inplace(&self, indices: &mut [usize]) { + for index in indices.iter_mut() { + *index = self.apply_index(*index); + } + } + fn unapply_indices(&self, indices: &[usize]) -> Vec; + fn is_identity(&self) -> bool { + self.mod_in() == 1 && self.mod_out() == 1 && self.dim_out() == 0 + } +} diff --git a/src/operator.rs b/src/operator.rs index c5bf0f7a8..23f757213 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -2,6 +2,7 @@ use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; use num::Integer; use std::rc::Rc; +use crate::UnsizedMapping; #[inline] const fn divmod(x: usize, y: usize) -> (usize, usize) { @@ -25,7 +26,7 @@ fn coordinates_iter_mut( }) } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Transpose(usize, usize); impl Transpose { @@ -34,20 +35,38 @@ impl Transpose { Self(len1, len2) } #[inline] - pub const fn len_out(&self) -> usize { - self.0 * self.1 + pub fn reverse(&mut self) { + std::mem::swap(&mut self.0, &mut self.1); } - #[inline] - pub const fn len_in(&self) -> usize { - self.0 * self.1 +} + +impl UnsizedMapping for Transpose { + fn dim_in(&self) -> usize { + 0 } - #[inline] - pub fn apply_index(&self, index: usize) -> usize { + fn delta_dim(&self) -> usize { + 0 + } + fn add_offset(&mut self, _offset: usize) {} + fn mod_in(&self) -> usize { + if self.0 != 1 && self.1 != 1 { + self.0 * self.1 + } else { + 1 + } + } + fn mod_out(&self) -> usize { + self.mod_in() + } + fn apply_inplace(&self, index: usize, _coordinates: &mut[f64], _stride: usize) -> usize { + self.apply_index(index) + } + fn apply_index(&self, index: usize) -> usize { let (j, k) = divmod(index, self.1); let (i, j) = divmod(j, self.0); (i * self.1 + k) * self.0 + j } - pub fn unapply_indices(&self, indices: &[usize]) -> Vec { + fn unapply_indices(&self, indices: &[usize]) -> Vec { indices .iter() .map(|k| { @@ -57,254 +76,249 @@ impl Transpose { }) .collect() } - #[inline] - const fn is_identity(&self) -> bool { - self.0 == 1 || self.1 == 1 - } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum IndexOperator { - Transpose(Transpose), - Take { - indices: Rc>, - nindices: usize, - len: usize, - }, +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Take { + indices: Rc>, + nindices: usize, + len: usize, } -impl IndexOperator { - #[inline] - pub const fn len_out(&self) -> usize { - match self { - Self::Transpose(transpose) => transpose.len_out(), - Self::Take { len, .. } => *len, +impl Take { + pub fn new(indices: impl Into>, len: usize) -> Self { + let indices = Rc::new(indices.into()); + let nindices = indices.len(); + Take { + indices, + nindices, + len, } } - #[inline] - pub const fn len_in(&self) -> usize { - match self { - Self::Transpose(transpose) => transpose.len_in(), - Self::Take { nindices, .. } => *nindices, - } +} + +impl UnsizedMapping for Take { + fn dim_in(&self) -> usize { + 0 } - pub fn apply_index(&self, index: usize) -> usize { - match self { - Self::Transpose(transpose) => transpose.apply_index(index), - Self::Take { - indices, - nindices, - len, - } => indices[index % nindices] + index / nindices * len, - } + fn delta_dim(&self) -> usize { + 0 } - pub fn unapply_indices(&self, indices: &[usize]) -> Vec { - match self { - Self::Transpose(transpose) => transpose.unapply_indices(indices), - Self::Take { - indices: take_indices, - nindices, - len, - } => indices - .iter() - .filter_map(|index| { - let (j, iout) = divmod(*index, *len); - let offset = j * nindices; - take_indices - .iter() - .position(|i| *i == iout) - .map(|iin| offset + iin) - }) - .collect(), - } + fn add_offset(&mut self, _offset: usize) {} + fn mod_in(&self) -> usize { + self.nindices } - #[inline] - const fn is_identity(&self) -> bool { - match self { - Self::Transpose(transpose) => transpose.is_identity(), - _ => false, - } + fn mod_out(&self) -> usize { + self.len + } + fn apply_inplace(&self, index: usize, _coordinates: &mut[f64], _stride: usize) -> usize { + self.apply_index(index) + } + fn apply_index(&self, index: usize) -> usize { + self.indices[index % self.nindices] + index / self.nindices * self.len + } + fn unapply_indices(&self, indices: &[usize]) -> Vec { + indices + .iter() + .filter_map(|index| { + let (j, iout) = divmod(*index, self.len); + let offset = j * self.nindices; + self.indices + .iter() + .position(|i| *i == iout) + .map(|iin| offset + iin) + }) + .collect() } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CoordinateOperator { - Children(Simplex), - Edges(Simplex), - UniformPoints { - points: Rc>, - npoints: usize, - point_dim: usize, - }, +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Children(Simplex, usize); + +impl Children { + pub fn new(simplex: Simplex) -> Self { + Self(simplex, 0) + } } -impl CoordinateOperator { - #[inline] - pub const fn dim_out(&self) -> usize { - match self { - Self::Children(simplex) => simplex.dim(), - Self::Edges(simplex) => simplex.dim(), - Self::UniformPoints { point_dim, .. } => *point_dim, - } +impl UnsizedMapping for Children { + fn dim_in(&self) -> usize { + self.0.dim() + self.1 } - #[inline] - pub const fn dim_in(&self) -> usize { - match self { - Self::Children(simplex) => simplex.dim(), - Self::Edges(simplex) => simplex.edge_dim(), - Self::UniformPoints { .. } => 0, - } + fn delta_dim(&self) -> usize { + 0 } - #[inline] - pub const fn delta_dim(&self) -> usize { - self.dim_out() - self.dim_in() + fn add_offset(&mut self, offset: usize) { + self.1 += offset; } - #[inline] - pub const fn len_out(&self) -> usize { + fn mod_in(&self) -> usize { + self.0.nchildren() + } + fn mod_out(&self) -> usize { 1 } - #[inline] - pub const fn len_in(&self) -> usize { - match self { - Self::Children(simplex) => simplex.nchildren(), - Self::Edges(simplex) => simplex.nedges(), - Self::UniformPoints { npoints, .. } => *npoints, - } + fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize { + self.0.apply_child(index, coordinates, stride, self.1) } - pub fn apply(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize { - match self { - Self::Children(simplex) => simplex.apply_child(index, coordinates, stride, offset), - Self::Edges(simplex) => simplex.apply_edge(index, coordinates, stride, offset), - Self::UniformPoints { - points, - npoints, - point_dim, - } => { - let dim = *point_dim; - let points: &[f64] = unsafe { std::mem::transmute(&points[..]) }; - let point = &points[(index % npoints) * dim..][..dim]; - for coord in coordinates_iter_mut(coordinates, stride, offset, dim, 0) { - coord.copy_from_slice(point); - } - index / npoints - } - } + fn apply_index(&self, index: usize) -> usize { + self.0.apply_child_index(index) } - pub fn unapply_indices(&self, indices: &[usize]) -> Vec { + fn unapply_indices(&self, indices: &[usize]) -> Vec { indices - .iter() - .map(|i| i * self.len_in()) - .flat_map(|i| (0..self.len_in()).map(move |j| i + j)) - .collect() - } - #[inline] - const fn is_identity(&self) -> bool { - false + .iter() + .map(|i| i * self.mod_in()) + .flat_map(|i| (0..self.mod_in()).map(move |j| i + j)) + .collect() } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Operator { - Index(IndexOperator), - Coordinate(CoordinateOperator, usize), +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Edges(pub Simplex, pub usize); + +impl Edges { + pub fn new(simplex: Simplex) -> Self { + Self(simplex, 0) + } } -impl Operator { - pub const fn new_transpose(len1: usize, len2: usize) -> Self { - Self::Index(IndexOperator::Transpose(Transpose::new(len1, len2))) +impl UnsizedMapping for Edges { + fn dim_in(&self) -> usize { + self.0.edge_dim() + self.1 } - pub fn new_take(indices: impl Into>, len: usize) -> Self { - let indices = Rc::new(indices.into()); - let nindices = indices.len(); - Self::Index(IndexOperator::Take { - indices, - nindices, - len, - }) + fn delta_dim(&self) -> usize { + 1 } - pub const fn new_children(simplex: Simplex) -> Self { - Self::Coordinate(CoordinateOperator::Children(simplex), 0) + fn add_offset(&mut self, offset: usize) { + self.1 += offset; } - pub const fn new_edges(simplex: Simplex) -> Self { - Self::Coordinate(CoordinateOperator::Edges(simplex), 0) + fn mod_in(&self) -> usize { + self.0.nedges() } - pub fn new_uniform_points(points: impl Into>, point_dim: usize) -> Self { + fn mod_out(&self) -> usize { + 1 + } + fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize { + self.0.apply_edge(index, coordinates, stride, self.1) + } + fn apply_index(&self, index: usize) -> usize { + self.0.apply_edge_index(index) + } + fn unapply_indices(&self, indices: &[usize]) -> Vec { + indices + .iter() + .map(|i| i * self.mod_in()) + .flat_map(|i| (0..self.mod_in()).map(move |j| i + j)) + .collect() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct UniformPoints { + points: Rc>, + npoints: usize, + point_dim: usize, + offset: usize, +} + +impl UniformPoints { + pub fn new(points: impl Into>, point_dim: usize) -> Self { let points: Rc> = Rc::new(unsafe { std::mem::transmute(points.into()) }); assert_eq!(points.len() % point_dim, 0); let npoints = points.len() / point_dim; - Self::Coordinate(CoordinateOperator::UniformPoints { + UniformPoints { points, npoints, point_dim, - }, 0) - } - #[inline] - pub const fn offset(&self) -> usize { - match self { - Self::Index(_) => 0, - Self::Coordinate(_, offset) => *offset, + offset: 0, } } - #[inline] - pub fn increment_offset(&mut self, amount: usize) { - match self { - Self::Index(_) => {}, - Self::Coordinate(_, offset) => *offset += amount, +} + +impl UnsizedMapping for UniformPoints { + fn dim_in(&self) -> usize { + self.offset + } + fn delta_dim(&self) -> usize { + self.point_dim + } + fn add_offset(&mut self, offset: usize) { + self.offset += offset; + } + fn mod_in(&self) -> usize { + self.npoints + } + fn mod_out(&self) -> usize { + 1 + } + fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize { + let points: &[f64] = unsafe { std::mem::transmute(&self.points[..]) }; + let point = &points[(index % self.npoints) * self.point_dim..][..self.point_dim]; + for coord in coordinates_iter_mut(coordinates, stride, self.offset, self.point_dim, 0) { + coord.copy_from_slice(point); } + index / self.npoints + } + fn apply_index(&self, index: usize) -> usize { + index / self.npoints + } + fn unapply_indices(&self, indices: &[usize]) -> Vec { + indices + .iter() + .map(|i| i * self.mod_in()) + .flat_map(|i| (0..self.mod_in()).map(move |j| i + j)) + .collect() } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Operator { + Transpose(Transpose), + Take(Take), + Children(Children), + Edges(Edges), + UniformPoints(UniformPoints), +} + +impl Operator { #[inline] - fn with_offset(self, offset: usize) -> Self { - match self { - Self::Coordinate(op, _) => Self::Coordinate(op, offset), - other => other, - } + pub fn new_transpose(len1: usize, len2: usize) -> Self { + Self::Transpose(Transpose::new(len1, len2)) } #[inline] - pub const fn dim_out(&self) -> usize { - match self { - Self::Index(_) => 0, - Self::Coordinate(op, _) => op.dim_out(), - } + pub fn new_take(indices: impl Into>, len: usize) -> Self { + Self::Take(Take::new(indices, len)) } #[inline] - pub const fn dim_in(&self) -> usize { - match self { - Self::Index(_) => 0, - Self::Coordinate(op, _) => op.dim_in(), - } + pub fn new_children(simplex: Simplex) -> Self { + Self::Children(Children::new(simplex)) } #[inline] - pub const fn delta_dim(&self) -> usize { - self.dim_out() - self.dim_in() + pub fn new_edges(simplex: Simplex) -> Self { + Self::Edges(Edges::new(simplex)) } #[inline] - pub const fn len_out(&self) -> usize { - match self { - Self::Index(op) => op.len_out(), - Self::Coordinate(op, _) => op.len_out(), - } + pub fn new_uniform_points(points: impl Into>, point_dim: usize) -> Self { + Self::UniformPoints(UniformPoints::new(points, point_dim)) } #[inline] - pub const fn len_in(&self) -> usize { + fn offset_mut(&mut self) -> Option<&mut usize> { match self { - Self::Index(op) => op.len_in(), - Self::Coordinate(op, _) => op.len_in(), + Self::Children(Children(_, ref mut offset)) => Some(offset), + Self::Edges(Edges(_, ref mut offset)) => Some(offset), + Self::UniformPoints(UniformPoints { ref mut offset, .. }) => Some(offset), + _ => None, } } - pub fn apply(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { - match self { - Self::Index(op) => op.apply_index(index), - Self::Coordinate(op, offset) => op.apply(index, coordinates, stride, *offset), - } + #[inline] + fn set_offset(&mut self, new_offset: usize) { + self.add_offset(new_offset); } - pub fn unapply_indices(&self, indices: &[usize]) -> Vec { - match self { - Self::Index(op) => op.unapply_indices(indices), - Self::Coordinate(op, _) => op.unapply_indices(indices), - } + #[inline] + pub fn with_offset(mut self, new_offset: usize) -> Self { + self.set_offset(new_offset); + self } pub fn shift_left(&self, operators: &[Self]) -> Option<(Option, Self, Vec)> { - use CoordinateOperator::{Children, Edges}; if self.is_transpose() { return None; } @@ -315,18 +329,18 @@ impl Operator { let mut stride_in = 1; for mut op in operators.iter().rev().cloned() { // Swap matching edges and children at the same offset. - if let Self::Coordinate(Edges(esimplex), eoffset) = &op { - if let Self::Coordinate(Children(ref mut csimplex), coffset) = &mut target { + if let Self::Edges(Edges(esimplex, eoffset)) = &op { + if let Self::Children(Children(ref mut csimplex, coffset)) = &mut target { if eoffset == coffset && esimplex.edge_dim() == csimplex.dim() { - if stride_in != 1 && self.len_in() != 1 { - shifted_ops.push(Self::new_transpose(stride_in, self.len_in())); + if stride_in != 1 && self.mod_in() != 1 { + shifted_ops.push(Self::new_transpose(stride_in, self.mod_in())); } shifted_ops.append(&mut queue); - if stride_out != 1 && self.len_in() != 1 { - shifted_ops.push(Self::new_transpose(self.len_in(), stride_out)); + if stride_out != 1 && self.mod_in() != 1 { + shifted_ops.push(Self::new_transpose(self.mod_in(), stride_out)); } shifted_ops.push(Self::new_take(esimplex.swap_edges_children_map(), esimplex.nedges() * esimplex.nchildren())); - shifted_ops.push(Self::Coordinate(Edges(*esimplex), *eoffset)); + shifted_ops.push(Self::Edges(Edges(*esimplex, *eoffset))); *csimplex = *esimplex; stride_in = 1; stride_out = 1; @@ -335,54 +349,56 @@ impl Operator { } } // Update strides. - if self.len_in() == 1 && self.len_out() == 1 { - } else if self.len_out() == 1 { - let n = stride_out.gcd(&op.len_in()); - stride_out = stride_out / n * op.len_out(); - stride_in *= op.len_in() / n; + if self.mod_in() == 1 && self.mod_out() == 1 { + } else if self.mod_out() == 1 { + let n = stride_out.gcd(&op.mod_in()); + stride_out = stride_out / n * op.mod_out(); + stride_in *= op.mod_in() / n; } else if let Some(Transpose(ref mut m, ref mut n)) = op.as_transpose_mut() { if stride_out % (*m * *n) == 0 { - } else if stride_out % *n == 0 && (*m * *n) % (stride_out * self.len_out()) == 0 { + } else if stride_out % *n == 0 && (*m * *n) % (stride_out * self.mod_out()) == 0 { stride_out /= *n; - *m = *m / self.len_out() * self.len_in(); - } else if *n % stride_out == 0 && *n % (stride_out * self.len_out()) == 0 { + *m = *m / self.mod_out() * self.mod_in(); + } else if *n % stride_out == 0 && *n % (stride_out * self.mod_out()) == 0 { stride_out *= *m; - *n = *n / self.len_out() * self.len_in(); + *n = *n / self.mod_out() * self.mod_in(); } else { return None; } - } else if stride_out % op.len_in() == 0 { - stride_out = stride_out / op.len_in() * op.len_out(); + } else if stride_out % op.mod_in() == 0 { + stride_out = stride_out / op.mod_in() * op.mod_out(); } else { return None; } // Update offsets. - if let Self::Coordinate(ref mut target, ref mut target_offset) = &mut target { - if let Self::Coordinate(ref mut op, ref mut op_offset) = &mut op { - if *op_offset + op.dim_in() <= *target_offset { - *target_offset += op.delta_dim(); - } else if *target_offset + target.dim_out() <= *op_offset { - *op_offset -= target.delta_dim(); - } else { - return None; - } + let op_delta_dim = op.delta_dim(); + let target_delta_dim = target.delta_dim(); + let op_dim_in = op.dim_in(); + let target_dim_out = target.dim_out(); + if let (Some(op_offset), Some(target_offset)) = (op.offset_mut(), target.offset_mut()) { + if op_dim_in <= *target_offset { + *target_offset += op_delta_dim; + } else if target_dim_out <= *op_offset { + *op_offset -= target_delta_dim; + } else { + return None; } } if !op.is_identity() { queue.push(op); } } - if stride_in != 1 && self.len_in() != 1 { - shifted_ops.push(Self::new_transpose(stride_in, self.len_in())); + if stride_in != 1 && self.mod_in() != 1 { + shifted_ops.push(Self::new_transpose(stride_in, self.mod_in())); } shifted_ops.extend(queue); - if stride_out != 1 && self.len_in() != 1 { - shifted_ops.push(Self::new_transpose(self.len_in(), stride_out)); + if stride_out != 1 && self.mod_in() != 1 { + shifted_ops.push(Self::new_transpose(self.mod_in(), stride_out)); } - let leading_transpose = if self.len_out() == 1 || stride_out == 1 { + let leading_transpose = if self.mod_out() == 1 || stride_out == 1 { None } else { - Some(Transpose::new(stride_out, self.len_out())) + Some(Transpose::new(stride_out, self.mod_out())) }; shifted_ops.reverse(); Some(( @@ -392,27 +408,84 @@ impl Operator { )) } #[inline] - const fn is_identity(&self) -> bool { - match self { - Self::Index(op) => op.is_identity(), - Self::Coordinate(op, _) => op.is_identity(), - } - } - #[inline] const fn is_transpose(&self) -> bool { - matches!(self, Self::Index(IndexOperator::Transpose(_))) + matches!(self, Self::Transpose(_)) } fn as_transpose_mut(&mut self) -> Option<&mut Transpose> { match self { - Self::Index(IndexOperator::Transpose(ref mut transpose)) => Some(transpose), + Self::Transpose(ref mut transpose) => Some(transpose), _ => None, } } } +macro_rules! dispatch { + ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { + match $self { + Operator::Transpose(var) => var.$fn($($arg),*), + Operator::Take(var) => var.$fn($($arg),*), + Operator::Children(var) => var.$fn($($arg),*), + Operator::Edges(var) => var.$fn($($arg),*), + Operator::UniformPoints(var) => var.$fn($($arg),*), + } + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { + match $self { + Operator::Transpose(var) => var.$fn($($arg),*), + Operator::Take(var) => var.$fn($($arg),*), + Operator::Children(var) => var.$fn($($arg),*), + Operator::Edges(var) => var.$fn($($arg),*), + Operator::UniformPoints(var) => var.$fn($($arg),*), + } + } + }; +} + +impl UnsizedMapping for Operator { + dispatch!{fn dim_in(&self) -> usize} + dispatch!{fn delta_dim(&self) -> usize} + dispatch!{fn add_offset(&mut self, offset: usize)} + dispatch!{fn mod_in(&self) -> usize} + dispatch!{fn mod_out(&self) -> usize} + dispatch!{fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize} + dispatch!{fn apply_index(&self, index: usize) -> usize} + dispatch!{fn apply_indices_inplace(&self, indices: &mut [usize])} + dispatch!{fn unapply_indices(&self, indices: &[usize]) -> Vec} + dispatch!{fn is_identity(&self) -> bool} +} + impl From for Operator { fn from(transpose: Transpose) -> Self { - Self::Index(IndexOperator::Transpose(transpose)) + Self::Transpose(transpose) + } +} + +impl From for Operator { + fn from(take: Take) -> Self { + Self::Take(take) + } +} + +impl From for Operator { + fn from(children: Children) -> Self { + Self::Children(children) + } +} + +impl From for Operator { + fn from(edges: Edges) -> Self { + Self::Edges(edges) + } +} + +impl From for Operator { + fn from(uniform_points: UniformPoints) -> Self { + Self::UniformPoints(uniform_points) } } @@ -442,7 +515,7 @@ mod tests { work[..incoord.len()].copy_from_slice(incoord); } } - assert_eq!(op.apply($inidx, &mut work, stride), $outidx); + assert_eq!(op.apply_inplace($inidx, &mut work, stride), $outidx); for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { assert_abs_diff_eq!(actual[..], desired[..]); } @@ -451,7 +524,7 @@ mod tests { use std::borrow::Borrow; let op = $op.borrow(); let mut work = Vec::with_capacity(0); - assert_eq!(op.apply($inidx, &mut work, op.dim_out()), $outidx); + assert_eq!(op.apply_inplace($inidx, &mut work, op.dim_out()), $outidx); }}; } @@ -516,13 +589,13 @@ mod tests { macro_rules! assert_unapply { ($op:expr) => {{ let op = $op; - let nin = 2 * op.len_in(); - let nout = 2 * op.len_out(); + let nin = 2 * op.mod_in(); + let nout = 2 * op.mod_out(); assert!(nout > 0); let mut map: Vec> = (0..nout).map(|_| Vec::new()).collect(); let mut work = Vec::with_capacity(0); for i in 0..nin { - map[op.apply(i, &mut work, op.offset() + op.dim_out())].push(i); + map[op.apply_inplace(i, &mut work, op.dim_out())].push(i); } for (j, desired) in map.into_iter().enumerate() { let mut actual = op.unapply_indices(&[j]); @@ -574,12 +647,12 @@ mod tests { for op in a.iter().rev() { let i = (1..) .into_iter() - .find(|i| (root_len * i) % op.len_in() == 0) + .find(|i| (root_len * i) % op.mod_in() == 0) .unwrap(); tip_len *= i; root_len *= i; - root_len = root_len / op.len_in() * op.len_out(); - assert!(op.offset() + op.dim_in() <= root_dim); + root_len = root_len / op.mod_in() * op.mod_out(); + assert!(op.dim_in() <= root_dim); root_dim += op.delta_dim(); } assert!(tip_len > 0); @@ -587,9 +660,9 @@ mod tests { let mut root_len_b = tip_len; let mut root_dim_b = tip_dim; for op in b.iter().rev() { - assert_eq!(root_len_b % op.len_in(), 0); - root_len_b = root_len_b / op.len_in() * op.len_out(); - assert!(op.offset() + op.dim_in() <= root_dim_b); + assert_eq!(root_len_b % op.mod_in(), 0); + root_len_b = root_len_b / op.mod_in() * op.mod_out(); + assert!(op.dim_in() <= root_dim_b); root_dim_b += op.delta_dim(); } assert_eq!(root_len_b, root_len); @@ -611,8 +684,8 @@ mod tests { for itip in 0..2 * tip_len { let mut crds_a = coords.clone(); let mut crds_b = coords.clone(); - let iroot_a = a.iter().rev().fold(itip, |i, op| op.apply(i, &mut crds_a, root_dim)); - let iroot_b = b.iter().rev().fold(itip, |i, op| op.apply(i, &mut crds_b, root_dim)); + let iroot_a = a.iter().rev().fold(itip, |i, op| op.apply_inplace(i, &mut crds_a, root_dim)); + let iroot_b = b.iter().rev().fold(itip, |i, op| op.apply_inplace(i, &mut crds_b, root_dim)); assert_eq!(iroot_a, iroot_b, "itip={itip}"); assert_abs_diff_eq!(crds_a[..], crds_b[..]); } diff --git a/src/sequence.rs b/src/sequence.rs index ffcdef64f..93e5fecf6 100644 --- a/src/sequence.rs +++ b/src/sequence.rs @@ -1,37 +1,25 @@ -use crate::chain::{Operator, UnsizedChain, UnsizedSequence as _}; -use crate::types::Dim; +use crate::operator::Operator; +use crate::chain::UnsizedChain; use std::iter; use std::ops::Mul; - -pub trait Sequence { - fn len(&self) -> usize; - fn is_empty(&self) -> bool { - self.len() == 0 - } - fn root_len(&self) -> usize; - fn dim(&self) -> Dim; - fn root_dim(&self) -> Dim; - fn apply_index(&self, index: usize) -> Option; - fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option; - fn apply(&self, index: usize, coordinates: &[f64]) -> Option<(usize, Vec)>; -} +use crate::{Sequence, UnsizedMapping}; #[derive(Debug, Clone)] pub struct Chain { - root_dim: Dim, + root_dim: usize, root_len: usize, chain: UnsizedChain, } impl Chain { - pub fn new(root_dim: Dim, root_len: usize, chain: impl Into) -> Self { + pub fn new(root_dim: usize, root_len: usize, chain: impl Into) -> Self { Self { root_dim, root_len, chain: chain.into(), } } - pub fn empty(dim: Dim, len: usize) -> Self { + pub fn empty(dim: usize, len: usize) -> Self { Self { root_dim: dim, root_len: len, @@ -45,46 +33,28 @@ impl Chain { impl Sequence for Chain { fn len(&self) -> usize { - let (n, d) = self.chain.delta_len(); - self.root_len * n / d + self.root_len * self.chain.mod_in() / self.chain.mod_out() } fn root_len(&self) -> usize { self.root_len } - fn dim(&self) -> Dim { + fn dim(&self) -> usize { self.root_dim - self.chain.delta_dim() } - fn root_dim(&self) -> Dim { + fn root_dim(&self) -> usize { self.root_dim } - fn apply_index(&self, index: usize) -> Option { - if index < self.len() { - let root_index = self.chain.apply_index(index); - assert!(root_index < self.root_len); - Some(root_index) - } else { - None - } + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { + self.chain.apply_inplace(index, coordinates, self.root_dim) } - fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { - if index < self.len() { - let root_index = self - .chain - .apply_many_inplace(index, coordinates, self.root_dim); - assert!(root_index < self.root_len); - Some(root_index) - } else { - None - } + fn apply_index_unchecked(&self, index: usize) -> usize { + self.chain.apply_index(index) } - fn apply(&self, index: usize, coordinates: &[f64]) -> Option<(usize, Vec)> { - if index < self.len() { - let (root_index, root_coords) = self.chain.apply_many(index, coordinates, self.dim()); - assert!(root_index < self.root_len); - Some((root_index, root_coords)) - } else { - None - } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.chain.apply_indices_inplace(indices) + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + self.chain.unapply_indices(indices) } } @@ -96,7 +66,7 @@ impl Mul for Chain { let root_len = self.root_len() * other.root_len(); let trans1 = Operator::new_transpose(other.root_len(), self.root_len()); let trans2 = Operator::new_transpose(self.len(), other.root_len()); - other.chain.increment_offset(self.dim()); + other.chain.add_offset(self.dim()); let chain: UnsizedChain = iter::once(trans1) .chain(self.chain.into_iter()) .chain(iter::once(trans2)) @@ -106,21 +76,18 @@ impl Mul for Chain { } } -fn get_root_indices(seq: impl Sequence) -> Vec { - (0..seq.len()).into_iter().map(|i| seq.apply_index(i).unwrap()).collect() -} - #[cfg(test)] mod tests { use super::*; - use crate::chain::{Children, Edges}; + use crate::operator::Operator; use crate::simplex::Simplex::*; #[test] fn test() { let mut chain = Chain::empty(1, 2); - chain.push(Children::new(Line, 0)); - assert_eq!(get_root_indices(chain), vec![0, 0, 1, 1]); + chain.push(Operator::new_children(Line)); + let indices: Vec = (0..chain.len()).into_iter().collect(); + assert_eq!(chain.apply_indices(&indices), Some(vec![0, 0, 1, 1])); // Find the root indices of the other tail. } } From bafc6b56c3fce1204ee8fe14446ee8119aa70fa7 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Sun, 19 Jun 2022 21:10:35 +0200 Subject: [PATCH 11/45] WIP --- src/chain.rs | 78 ++++++++- src/lib.rs | 33 ++-- src/operator.rs | 9 +- src/sequence.rs | 420 +++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 479 insertions(+), 61 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index e015ba865..d5ae5a845 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -2,6 +2,7 @@ use crate::operator::{Operator, Transpose, Edges}; use crate::simplex::Simplex; use std::collections::BTreeMap; use crate::UnsizedMapping; +use num::Integer as _; #[derive(Debug, Clone, PartialEq, Eq)] pub struct UnsizedChain(Vec); @@ -12,9 +13,34 @@ impl UnsizedChain { Self(operators) } #[inline] - pub fn empty() -> Self { + pub fn identity() -> Self { UnsizedChain(Vec::new()) } + fn dim_out_in(&self) -> (usize, usize) { + let mut dim_in = 0; + let mut dim_out = 0; + for op in self.0.iter().rev() { + if let Some(n) = op.dim_in().checked_sub(dim_out) { + dim_in += n; + dim_out += n; + } + dim_out += op.delta_dim(); + } + (dim_out, dim_in) + } + fn mod_out_in(&self) -> (usize, usize) { + let mut mod_out = 1; + let mut mod_in = 1; + for op in self.0.iter().rev() { + let n = mod_out.lcm(&op.mod_in()); + mod_in *= n / mod_out; + mod_out = n / op.mod_in() * op.mod_out(); + } + (mod_out, mod_in) + } + pub fn iter(&self) -> impl Iterator + DoubleEndedIterator { + self.0.iter() + } pub fn push(&mut self, operator: impl Into) { self.0.push(operator.into()) } @@ -94,11 +120,31 @@ impl UnsizedChain { }; (common, tail1, tail2) } + pub fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self) { + let (common, tail1, tail2) = self.remove_common_prefix(other); + // Move transposes at the front of `tail1` to `tail2`. + let mut tail1: Vec<_> = tail1.into(); + let mut tail2: Vec<_> = tail2.into(); + tail1.reverse(); + tail2.reverse(); + while let Some(mut op) = tail1.pop() { + if let Some(transpose) = op.as_transpose_mut() { + transpose.reverse(); + tail2.push(op); + } else { + tail1.push(op); + break; + } + } + tail1.reverse(); + tail2.reverse(); + (common, tail1.into(), tail2.into()) + } } impl UnsizedMapping for UnsizedChain { fn dim_in(&self) -> usize { - self.0.iter().map(|op| op.dim_in()).sum() + self.dim_out_in().1 } fn delta_dim(&self) -> usize { self.0.iter().map(|op| op.delta_dim()).sum() @@ -108,11 +154,11 @@ impl UnsizedMapping for UnsizedChain { op.add_offset(offset); } } - fn mod_out(&self) -> usize { - self.0.iter().map(|op| op.mod_out()).product() - } fn mod_in(&self) -> usize { - self.0.iter().map(|op| op.mod_in()).product() + self.mod_out_in().1 + } + fn mod_out(&self) -> usize { + self.mod_out_in().0 } fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { self.0 @@ -153,6 +199,24 @@ impl IntoIterator for UnsizedChain { } } +impl From<[Operator; N]> for UnsizedChain { + fn from(operators: [Operator; N]) -> Self { + operators.into_iter().collect() + } +} + +impl From> for UnsizedChain { + fn from(operators: Vec) -> Self { + Self(operators) + } +} + +impl From for Vec { + fn from(chain: UnsizedChain) -> Self { + chain.0 + } +} + ///// A chain of [`Operator`]s. //#[derive(Debug, Clone, PartialEq)] //pub struct UnsizedChain { @@ -755,7 +819,7 @@ mod tests { a.remove_common_prefix(&b), ( UnsizedChain::new(vec![c1.clone(), c1.clone()]), - UnsizedChain::empty(), + UnsizedChain::identity(), UnsizedChain::new(vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()]), ) ); diff --git a/src/lib.rs b/src/lib.rs index 921d06e00..f4ad975c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,20 +4,18 @@ pub mod operator; pub mod simplex; pub mod sequence; -pub trait Sequence { - fn len(&self) -> usize; - fn is_empty(&self) -> bool { - self.len() == 0 - } - fn root_len(&self) -> usize; - fn dim(&self) -> usize; - fn root_dim(&self) -> usize; - fn delta_dim(&self) -> usize { - self.root_dim() - self.dim() +pub trait Mapping { + fn len_out(&self) -> usize; + fn len_in(&self) -> usize; + fn dim_out(&self) -> usize { + self.dim_in() + self.delta_dim() } + fn dim_in(&self) -> usize; + fn delta_dim(&self) -> usize; + fn add_offset(&mut self, offset: usize); fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize; fn apply_inplace(&self, index: usize, coordinates: &mut[f64]) -> Option { - if index < self.len() { + if index < self.len_in() { Some(self.apply_inplace_unchecked(index, coordinates)) } else { None @@ -25,15 +23,19 @@ pub trait Sequence { } fn apply_index_unchecked(&self, index: usize) -> usize; fn apply_index(&self, index: usize) -> Option { - if index < self.len() { + if index < self.len_in() { Some(self.apply_index_unchecked(index)) } else { None } } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]); + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + for index in indices.iter_mut() { + *index = self.apply_index_unchecked(*index); + } + } fn apply_indices(&self, indices: &[usize]) -> Option> { - if indices.iter().all(|index| *index < self.len()) { + if indices.iter().all(|index| *index < self.len_in()) { let mut indices = indices.to_vec(); self.apply_indices_inplace_unchecked(&mut indices); Some(indices) @@ -43,12 +45,13 @@ pub trait Sequence { } fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec; fn unapply_indices(&self, indices: &[usize]) -> Option> { - if indices.iter().all(|index| *index < self.len()) { + if indices.iter().all(|index| *index < self.len_out()) { Some(self.unapply_indices_unchecked(indices)) } else { None } } + fn is_identity(&self) -> bool; } pub trait UnsizedMapping { diff --git a/src/operator.rs b/src/operator.rs index 23f757213..efed34131 100644 --- a/src/operator.rs +++ b/src/operator.rs @@ -224,6 +224,7 @@ impl UniformPoints { pub fn new(points: impl Into>, point_dim: usize) -> Self { let points: Rc> = Rc::new(unsafe { std::mem::transmute(points.into()) }); assert_eq!(points.len() % point_dim, 0); + assert_ne!(point_dim, 0); let npoints = points.len() / point_dim; UniformPoints { points, @@ -411,7 +412,13 @@ impl Operator { const fn is_transpose(&self) -> bool { matches!(self, Self::Transpose(_)) } - fn as_transpose_mut(&mut self) -> Option<&mut Transpose> { + pub fn as_transpose(&self) -> Option<&Transpose> { + match self { + Self::Transpose(transpose) => Some(transpose), + _ => None, + } + } + pub fn as_transpose_mut(&mut self) -> Option<&mut Transpose> { match self { Self::Transpose(ref mut transpose) => Some(transpose), _ => None, diff --git a/src/sequence.rs b/src/sequence.rs index 93e5fecf6..ccbeea4df 100644 --- a/src/sequence.rs +++ b/src/sequence.rs @@ -1,51 +1,99 @@ -use crate::operator::Operator; use crate::chain::UnsizedChain; +use crate::operator::Operator; +use crate::{Mapping, UnsizedMapping}; use std::iter; use std::ops::Mul; -use crate::{Sequence, UnsizedMapping}; #[derive(Debug, Clone)] pub struct Chain { - root_dim: usize, - root_len: usize, + dim_out: usize, + dim_in: usize, + len_out: usize, + len_in: usize, chain: UnsizedChain, } impl Chain { - pub fn new(root_dim: usize, root_len: usize, chain: impl Into) -> Self { + pub fn new(dim_out: usize, len_out: usize, chain: UnsizedChain) -> Self { + assert!(chain.dim_out() <= dim_out); + assert_eq!(len_out % chain.mod_out(), 0); + let len_in = len_out / chain.mod_out() * chain.mod_in(); + let dim_in = dim_out - chain.delta_dim(); Self { - root_dim, - root_len, - chain: chain.into(), + dim_out, + dim_in, + len_out, + len_in, + chain, } } - pub fn empty(dim: usize, len: usize) -> Self { + pub fn identity(dim: usize, len: usize) -> Self { Self { - root_dim: dim, - root_len: len, - chain: UnsizedChain::empty(), + dim_out: dim, + dim_in: dim, + len_out: len, + len_in: len, + chain: UnsizedChain::identity(), + } + } + pub fn iter(&self) -> impl Iterator + DoubleEndedIterator { + self.chain.iter() + } + pub fn push(&mut self, operator: Operator) { + assert!(operator.dim_out() <= self.dim_in); + assert_eq!(self.len_in % operator.mod_out(), 0); + self.dim_in -= operator.delta_dim(); + self.len_in = self.len_in / operator.mod_out() * operator.mod_in(); + self.chain.push(operator); + } + pub fn partial_relative_to(&self, target: &Self) -> Option<(Self, Option>)> { + let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); + let rel = Chain::new(target.dim_in(), target.len_in(), rel.into()); + if rem.dim_out() != 0 { + None + } else if rem.is_identity() { + Some((rel, None)) + } else { + let mut rem_indices: Vec = (0..).take(target.len_in()).collect(); + rem.apply_indices_inplace(&mut rem_indices); + let rel_indices = rel.unapply_indices_unchecked(&rem_indices); + Some((rel, Some(rel_indices))) } } - pub fn push(&mut self, operator: impl Into) { - self.chain.push(operator.into()); + pub fn join(&self, other: &Self) -> Option { + // TODO: return Result + if self.dim_in() == other.dim_out() && self.len_in() == other.len_out() { + let ops = self.iter().chain(other.iter()).cloned().collect(); + Some(Chain::new(self.dim_out, self.len_out, ops)) + } else { + None + } } } -impl Sequence for Chain { - fn len(&self) -> usize { - self.root_len * self.chain.mod_in() / self.chain.mod_out() +impl Mapping for Chain { + fn len_in(&self) -> usize { + self.len_in + } + fn len_out(&self) -> usize { + self.len_out } - fn root_len(&self) -> usize { - self.root_len + fn dim_in(&self) -> usize { + self.dim_in } - fn dim(&self) -> usize { - self.root_dim - self.chain.delta_dim() + fn dim_out(&self) -> usize { + self.dim_out } - fn root_dim(&self) -> usize { - self.root_dim + fn delta_dim(&self) -> usize { + self.dim_out - self.dim_in + } + fn add_offset(&mut self, offset: usize) { + self.chain.add_offset(offset); + self.dim_out += offset; + self.dim_in += offset; } fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - self.chain.apply_inplace(index, coordinates, self.root_dim) + self.chain.apply_inplace(index, coordinates, self.dim_out) } fn apply_index_unchecked(&self, index: usize) -> usize { self.chain.apply_index(index) @@ -56,23 +104,241 @@ impl Sequence for Chain { fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { self.chain.unapply_indices(indices) } + fn is_identity(&self) -> bool { + self.chain.is_identity() + } } impl Mul for Chain { type Output = Self; fn mul(self, mut other: Self) -> Self { - let root_dim = self.root_dim() + other.root_dim(); - let root_len = self.root_len() * other.root_len(); - let trans1 = Operator::new_transpose(other.root_len(), self.root_len()); - let trans2 = Operator::new_transpose(self.len(), other.root_len()); - other.chain.add_offset(self.dim()); + let dim_out = self.dim_out() + other.dim_out(); + let len_out = self.len_out() * other.len_out(); + let trans1 = Operator::new_transpose(other.len_out(), self.len_out()); + let trans2 = Operator::new_transpose(self.len_in(), other.len_out()); + other.add_offset(self.dim_in()); let chain: UnsizedChain = iter::once(trans1) - .chain(self.chain.into_iter()) + .chain(self.chain) .chain(iter::once(trans2)) - .chain(other.chain.into_iter()) + .chain(other.chain) .collect(); - Chain::new(root_dim, root_len, chain) + Chain::new(dim_out, len_out, chain) + } +} + +#[derive(Debug, Clone)] +struct Concat { + dim_in: usize, + delta_dim: usize, + len_out: usize, + len_in: usize, + items: Vec, +} + +impl Concat { + pub fn new(items: Vec) -> Self { + // TODO: Return `Result`. + let first = items.first().unwrap(); + let dim_in = first.dim_in(); + let delta_dim = first.delta_dim(); + let len_out = first.len_out(); + let mut len_in = 0; + for item in items.iter() { + assert_eq!(item.dim_in(), dim_in); + assert_eq!(item.delta_dim(), delta_dim); + assert_eq!(item.len_out(), len_out); + len_in += item.len_in(); + } + Self { + dim_in, + delta_dim, + len_out, + len_in, + items, + } + } + // pub fn relative_to(&self, target: &Self) -> Option { + // let mut parts = Vec::new(); + // let mut map = iter::repeat((0, 0)).take(self.len_in()).collect(); + // for item in self.items.iter() { + // let mut seen: Vec = iter::repeat(false).take(self.len_in()).collect(); + // let mut offset = 0; + // for titem in target.items.iter() { + // if let Some(rel, indices) = item.relative_to(titem) { + // unimplemented!{} + // } + // offset += titem.len_in(); + // } + // if !seen.all(|c| c) { + // return None; + // } + // } + // RelativeParts { + // dim_in: self.dim_in(), + // delta_dim: target.dim_in() - self.dim_in(), + // len_out: target.len_in(), + // map, + // parts, + // } + // } + fn resolve_item_unchecked(&self, mut index: usize) -> (&Item, usize) { + for item in self.items.iter() { + if index < item.len_in() { + return (item, index); + } + index -= item.len_in(); + } + panic!("index out of range"); + } +} + +impl Mapping for Concat { + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn len_out(&self) -> usize { + self.len_out + } + fn len_in(&self) -> usize { + self.len_in + } + fn add_offset(&mut self, offset: usize) { + for item in self.items.iter_mut() { + item.add_offset(offset); + } + self.dim_in += offset; + } + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { + let (item, index) = self.resolve_item_unchecked(index); + item.apply_inplace_unchecked(index, coordinates) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + let (item, index) = self.resolve_item_unchecked(index); + item.apply_index_unchecked(index) + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + unimplemented! {} + } + fn is_identity(&self) -> bool { + false + } +} + +trait RelativeTo { + type Output: Mapping; + + fn relative_to(&self, target: &Target) -> Option; +} + +impl RelativeTo for Chain { + type Output = Self; + + fn relative_to(&self, target: &Self) -> Option { + let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); + rem.is_identity() + .then(|| Chain::new(target.dim_in(), target.len_in(), rel.into())) + } +} + +impl RelativeTo for Concat +where + Item: Mapping + RelativeTo, + Target: Mapping, +{ + type Output = Concat; + + fn relative_to(&self, target: &Target) -> Option { + self.items + .iter() + .map(|item| item.relative_to(target)) + .collect::>() + .map(|rel_items| Concat::new(rel_items)) + } +} + +impl RelativeTo> for Chain { + type Output = RelativeToConcatChain; + + fn relative_to(&self, targets: &Concat) -> Option { + let mut rels_indices = Vec::new(); + let mut offset = 0; + for (itarget, target) in targets.items.iter().enumerate() { + let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); + // if rem.is_identity() { + // return rel + offset; + // } + if rem.dim_out() == 0 { + let mut rem_indices: Vec = (0..).take(target.len_in()).collect(); + rem.apply_indices_inplace(&mut rem_indices); + // TODO: First collect rem_indices for all targets, then remove + // the common tail from all rels and then unapply the rem + // indices on the rels. + rels_indices.push((rel, itarget, rem_indices, offset)) + } + offset += target.len_in(); + } + // TODO: Split off common tail. + // TODO: unapply indices and build map + // let rel_indices = rel.unapply_indices_unchecked(&rem_indices); + // if !rel_indices.is_empty() { + // // update map + // //tails.push((rel, offset, rel_indices)); + // } + // TODO: return None if we didn't find everything + unimplemented!{} + // RelativeToConcatChain { ... } + // Pair>>, Chain> + } +} + +#[derive(Debug, Clone)] +struct RelativeToConcatChain { + dim_in: usize, + delta_dim: usize, + len_out: usize, + map: Vec<(usize, usize)>, + parts: Vec<(UnsizedChain, usize)>, + // common_tail: UnsizedChain, +} + +impl Mapping for RelativeToConcatChain { + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn len_out(&self) -> usize { + self.len_out + } + fn len_in(&self) -> usize { + self.map.len() + } + fn add_offset(&mut self, offset: usize) { + for (chain, _) in self.parts.iter_mut() { + chain.add_offset(offset); + } + self.dim_in += offset; + } + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { + let (ipart, index) = self.map[index]; + let (chain, offset) = &self.parts[ipart]; + offset + chain.apply_inplace(index, coordinates, self.dim_out()) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + let (ipart, index) = self.map[index]; + let (chain, offset) = &self.parts[ipart]; + offset + chain.apply_index(index) + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + unimplemented! {} + } + fn is_identity(&self) -> bool { + false } } @@ -81,13 +347,91 @@ mod tests { use super::*; use crate::operator::Operator; use crate::simplex::Simplex::*; + use approx::assert_abs_diff_eq; + + macro_rules! chain { + (dim=$dim_out:literal, len=$len_out:literal) => { + Chain::identity($dim_out, $len_out) + }; + (dim=$dim_out:literal, len=$len_out:literal <- $($op:expr),*) => { + Chain::new($dim_out, $len_out, vec![$(Operator::from($op)),*].into()) + }; + } + + macro_rules! assert_equiv_chains { + ($a:expr, $b:expr $(, $simplex:ident)*) => {{ + let a = $a; + let b = $b; + println!("a: {a:?}"); + println!("b: {b:?}"); + // Build coords: the outer product of the vertices of the given simplices, zero-padded + // to the dimension of the root. + let coords = iter::once([]); + let simplex_dim = 0; + $( + let coords = coords.flat_map(|coord| { + $simplex + .vertices() + .chunks($simplex.dim()) + .map(move |vert| [&coord, vert].concat()) + }); + let simplex_dim = simplex_dim + $simplex.dim(); + )* + assert_eq!(simplex_dim, a.dim_in(), "given simplices don't add up to the input dimension"); + let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); + let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); + // Test if every input maps to the same output for both `a` and `b`. + for i in 0..2 * a.len_in() { + let mut crds_a = coords.clone(); + let mut crds_b = coords.clone(); + let ja = a.apply_inplace(i, &mut crds_a); + let jb = b.apply_inplace(i, &mut crds_b); + assert_eq!(ja, jb, "i={i}"); + assert_abs_diff_eq!(crds_a[..], crds_b[..]); + } + }}; + } + + macro_rules! assert_partial_relative_to { + ($a:expr, $b:expr $(, $simplex:ident)*) => {{ + let a = $a; + let b = $b; + let (rel, indices) = b.partial_relative_to(&a).unwrap(); + + let (rel_compressed, b_compressed) = if let Some(indices) = indices { + let b_indices: Vec<_> = (0..).take(b.len_in()).filter_map(|i| indices.contains(&i).then(|| i)).collect(); + let mut tmp: Vec<_> = (0..rel.len_in()).collect(); + tmp.sort_by_key(|&i| &indices[i]); + let mut rel_indices: Vec<_> = (0..rel.len_in()).collect(); + rel_indices.sort_by_key(|&i| &tmp[i]); + let mut b = b.clone(); + b.push(Operator::new_take(b_indices, b.len_in())); + let mut rel = rel.clone(); + rel.push(Operator::new_take(rel_indices, rel.len_in())); + (rel, b) + } else { + (rel, b) + }; + + assert_equiv_chains!(a.join(&rel_compressed).unwrap(), &b_compressed $(, $simplex)*); + }}; + } #[test] - fn test() { - let mut chain = Chain::empty(1, 2); - chain.push(Operator::new_children(Line)); - let indices: Vec = (0..chain.len()).into_iter().collect(); - assert_eq!(chain.apply_indices(&indices), Some(vec![0, 0, 1, 1])); - // Find the root indices of the other tail. + fn partial_relative_to() { + use crate::operator::*; + assert_partial_relative_to!( + chain!(dim=1, len=2 <- Children::new(Line), Take::new([0,3,1], 4)), + chain!(dim=1, len=2 <- Children::new(Line), Children::new(Line)), + Line + ); + assert_partial_relative_to!( + chain!(dim=0, len=4 <- Take::new([0,3,1], 4)), + chain!(dim = 0, len = 4) + ); + assert_partial_relative_to!( + chain!(dim=1, len=2 <- Children::new(Line)), + chain!(dim=1, len=2 <- Edges::new(Line)) + ); } } From ee4fabfe5a13ce1ecab9b7138d8c70fa760a72b2 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Mon, 20 Jun 2022 11:16:19 +0200 Subject: [PATCH 12/45] WIP --- src/chain.rs | 871 ++++------------------------- src/{operator.rs => elementary.rs} | 310 +++++----- src/lib.rs | 8 +- src/sequence.rs | 374 ++++++++----- 4 files changed, 525 insertions(+), 1038 deletions(-) rename src/{operator.rs => elementary.rs} (65%) diff --git a/src/chain.rs b/src/chain.rs index d5ae5a845..4e6b11906 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,103 +1,127 @@ -use crate::operator::{Operator, Transpose, Edges}; +use crate::elementary::{Edges, Elementary, Transpose}; use crate::simplex::Simplex; -use std::collections::BTreeMap; use crate::UnsizedMapping; use num::Integer as _; +use std::collections::BTreeMap; +use std::ops::{Deref, DerefMut}; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UnsizedChain(Vec); +fn dim_out_in(items: &[M]) -> (usize, usize) { + let mut dim_in = 0; + let mut dim_out = 0; + for item in items.iter().rev() { + if let Some(n) = item.dim_in().checked_sub(dim_out) { + dim_in += n; + dim_out += n; + } + dim_out += item.delta_dim(); + } + (dim_out, dim_in) +} -impl UnsizedChain { - #[inline] - pub fn new(operators: Vec) -> Self { - Self(operators) +fn mod_out_in(items: &[M]) -> (usize, usize) { + let mut mod_out = 1; + let mut mod_in = 1; + for item in items.iter().rev() { + let n = mod_out.lcm(&item.mod_in()); + mod_in *= n / mod_out; + mod_out = n / item.mod_in() * item.mod_out(); + } + (mod_out, mod_in) +} + +impl UnsizedMapping for T +where + M: UnsizedMapping, + T: Deref + DerefMut, +{ + fn dim_in(&self) -> usize { + dim_out_in(self.deref()).1 } - #[inline] - pub fn identity() -> Self { - UnsizedChain(Vec::new()) + fn delta_dim(&self) -> usize { + self.iter().map(|item| item.delta_dim()).sum() } - fn dim_out_in(&self) -> (usize, usize) { - let mut dim_in = 0; - let mut dim_out = 0; - for op in self.0.iter().rev() { - if let Some(n) = op.dim_in().checked_sub(dim_out) { - dim_in += n; - dim_out += n; - } - dim_out += op.delta_dim(); + fn add_offset(&mut self, offset: usize) { + for item in self.iter_mut() { + item.add_offset(offset); } - (dim_out, dim_in) } - fn mod_out_in(&self) -> (usize, usize) { - let mut mod_out = 1; - let mut mod_in = 1; - for op in self.0.iter().rev() { - let n = mod_out.lcm(&op.mod_in()); - mod_in *= n / mod_out; - mod_out = n / op.mod_in() * op.mod_out(); - } - (mod_out, mod_in) + fn mod_in(&self) -> usize { + mod_out_in(self.deref()).1 + } + fn mod_out(&self) -> usize { + mod_out_in(self.deref()).0 + } + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { + self.iter().rev().fold(index, |index, item| { + item.apply_inplace(index, coordinates, stride) + }) + } + fn apply_index(&self, index: usize) -> usize { + self.iter() + .rev() + .fold(index, |index, item| item.apply_index(index)) } - pub fn iter(&self) -> impl Iterator + DoubleEndedIterator { - self.0.iter() + fn apply_indices_inplace(&self, indices: &mut [usize]) { + for item in self.iter().rev() { + item.apply_indices_inplace(indices); + } } - pub fn push(&mut self, operator: impl Into) { - self.0.push(operator.into()) + fn unapply_indices(&self, indices: &[usize]) -> Vec { + self.iter().fold(indices.to_vec(), |indices, item| { + item.unapply_indices(&indices) + }) } - fn split_heads(&self) -> BTreeMap, Vec)> { - let mut heads = BTreeMap::new(); - for (i, op) in self.0.iter().enumerate() { - if let Some((transpose, head, mut tail)) = op.shift_left(&self.0[..i]) { - tail.extend(self.0[i + 1..].iter().cloned()); +} + +pub trait RemoveCommonPrefix: Sized { + fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self); + fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self); +} + +fn split_heads( + items: &[Elementary], +) -> BTreeMap, Vec)> { + let mut heads = BTreeMap::new(); + for (i, item) in items.iter().enumerate() { + if let Some((transpose, head, mut tail)) = item.shift_left(&items[..i]) { + tail.extend(items[i + 1..].iter().cloned()); + heads.insert(head, (transpose, tail)); + } + if let Elementary::Edges(Edges(Simplex::Line, offset)) = item { + let children = Elementary::new_children(Simplex::Line).with_offset(*offset); + if let Some((transpose, head, mut tail)) = children.shift_left(&items[..i]) { + tail.push(item.clone()); + tail.push(Elementary::new_take( + Simplex::Line.swap_edges_children_map(), + Simplex::Line.nedges() * Simplex::Line.nchildren(), + )); + tail.extend(items[i + 1..].iter().cloned()); heads.insert(head, (transpose, tail)); } - if let Operator::Edges(Edges(Simplex::Line, offset)) = op { - let children = Operator::new_children(Simplex::Line).with_offset(*offset); - if let Some((transpose, head, mut tail)) = children.shift_left(&self.0[..i]) { - tail.push(op.clone()); - tail.push(Operator::new_take( - Simplex::Line.swap_edges_children_map(), - Simplex::Line.nedges() * Simplex::Line.nchildren(), - )); - tail.extend(self.0[i + 1..].iter().cloned()); - heads.insert(head, (transpose, tail)); - } - } } - heads } + heads +} + +impl RemoveCommonPrefix for Vec { /// Remove and return the common prefix of two chains, transforming either if necessary. - pub fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { + fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { let mut common = Vec::new(); let mut tail1 = self.clone(); let mut tail2 = other.clone(); - while !tail1.0.is_empty() && !tail2.0.is_empty() { - if tail1.0.last() == tail2.0.last() { - common.push(tail1.0.pop().unwrap()); - tail2.0.pop(); + while !tail1.is_empty() && !tail2.is_empty() { + if tail1.last() == tail2.last() { + common.push(tail1.pop().unwrap()); + tail2.pop(); continue; } - let heads1: BTreeMap> = tail1 - .split_heads() + let heads1: BTreeMap> = split_heads(&tail1) .into_iter() - .filter_map(|(head, (trans, tail))| { - if trans.is_none() { - Some((head, tail)) - } else { - None - } - }) + .filter_map(|(head, (trans, tail))| trans.is_none().then(|| (head, tail))) .collect(); - let mut heads2: BTreeMap> = tail2 - .split_heads() + let mut heads2: BTreeMap> = split_heads(&tail2) .into_iter() - .filter_map(|(head, (trans, tail))| { - if trans.is_none() { - Some((head, tail)) - } else { - None - } - }) + .filter_map(|(head, (trans, tail))| trans.is_none().then(|| (head, tail))) .collect(); if let Some((head, new_tail1, new_tail2)) = heads1 .into_iter() @@ -105,34 +129,34 @@ impl UnsizedChain { .min_by_key(|(_, t1, t2)| std::cmp::max(t1.len(), t2.len())) { common.push(head); - tail1.0 = new_tail1; - tail2.0 = new_tail2; + tail1 = new_tail1; + tail2 = new_tail2; continue; } break; } - let common = if tail1.0.is_empty() && (!tail2.0.is_empty() || self.0.len() <= other.0.len()) { + let common = if tail1.is_empty() && (!tail2.is_empty() || self.len() <= other.len()) { self.clone() - } else if tail2.0.is_empty() { + } else if tail2.is_empty() { other.clone() } else { - Self::new(common) + common }; (common, tail1, tail2) } - pub fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self) { + fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self) { let (common, tail1, tail2) = self.remove_common_prefix(other); // Move transposes at the front of `tail1` to `tail2`. let mut tail1: Vec<_> = tail1.into(); let mut tail2: Vec<_> = tail2.into(); tail1.reverse(); tail2.reverse(); - while let Some(mut op) = tail1.pop() { - if let Some(transpose) = op.as_transpose_mut() { + while let Some(mut item) = tail1.pop() { + if let Some(transpose) = item.as_transpose_mut() { transpose.reverse(); - tail2.push(op); + tail2.push(item); } else { - tail1.push(op); + tail1.push(item); break; } } @@ -142,667 +166,6 @@ impl UnsizedChain { } } -impl UnsizedMapping for UnsizedChain { - fn dim_in(&self) -> usize { - self.dim_out_in().1 - } - fn delta_dim(&self) -> usize { - self.0.iter().map(|op| op.delta_dim()).sum() - } - fn add_offset(&mut self, offset: usize) { - for op in self.0.iter_mut() { - op.add_offset(offset); - } - } - fn mod_in(&self) -> usize { - self.mod_out_in().1 - } - fn mod_out(&self) -> usize { - self.mod_out_in().0 - } - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { - self.0 - .iter() - .rev() - .fold(index, |index, op| op.apply_inplace(index, coordinates, stride)) - } - fn apply_index(&self, index: usize) -> usize { - self.0.iter().rev().fold(index, |index, op| op.apply_index(index)) - } - fn apply_indices_inplace(&self, indices: &mut [usize]) { - for op in self.0.iter().rev() { - op.apply_indices_inplace(indices); - } - } - fn unapply_indices(&self, indices: &[usize]) -> Vec { - self.0 - .iter() - .fold(indices.to_vec(), |indices, op| op.unapply_indices(&indices)) - } -} - -impl FromIterator for UnsizedChain { - fn from_iter(iter: T) -> Self - where - T: IntoIterator, - { - Self::new(iter.into_iter().collect()) - } -} - -impl IntoIterator for UnsizedChain { - type Item = Operator; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl From<[Operator; N]> for UnsizedChain { - fn from(operators: [Operator; N]) -> Self { - operators.into_iter().collect() - } -} - -impl From> for UnsizedChain { - fn from(operators: Vec) -> Self { - Self(operators) - } -} - -impl From for Vec { - fn from(chain: UnsizedChain) -> Self { - chain.0 - } -} - -///// A chain of [`Operator`]s. -//#[derive(Debug, Clone, PartialEq)] -//pub struct UnsizedChain { -// rev_operators: Vec, -//} -// -//impl UnsizedChain { -// #[inline] -// pub fn new(operators: Operators) -> Self -// where -// Operators: IntoIterator, -// Operators::IntoIter: DoubleEndedIterator, -// { -// UnsizedChain { -// rev_operators: operators.into_iter().rev().collect(), -// } -// } -// #[inline] -// pub fn empty() -> Self { -// UnsizedChain { -// rev_operators: Vec::new(), -// } -// } -// pub fn push(&mut self, operator: impl Into) { -// self.rev_operators.insert(0, operator.into()) -// } -// /// Returns a clone of this [`UnsizedChain`] with the given `operator` appended. -// #[inline] -// pub fn clone_and_push(&self, operator: Operator) -> Self { -// Self::new( -// self.rev_operators -// .iter() -// .rev() -// .cloned() -// .chain(iter::once(operator)), -// ) -// } -// #[inline] -// pub fn iter(&self) -> impl Iterator + DoubleEndedIterator { -// self.rev_operators.iter().rev() -// } -// fn split_heads(&self) -> BTreeMap> { -// let mut heads = BTreeMap::new(); -// 'a: for (i, head) in self.rev_operators.iter().enumerate() { -// let mut rev_tail: Vec<_> = self.rev_operators.iter().take(i).cloned().collect(); -// let mut head = head.clone(); -// for op in self.rev_operators.iter().skip(i + 1) { -// if let Some(ops) = swap(op, &mut head) { -// rev_tail.extend(ops.into_iter().rev()); -// } else { -// continue 'a; -// } -// } -// heads.insert(head, rev_tail); -// } -// 'b: for (i, op) in self.rev_operators.iter().enumerate() { -// if let Operator::Edges(Edges { -// simplex: Simplex::Line, -// offset, -// }) = op -// { -// let simplex = Simplex::Line; -// let mut rev_tail: Vec<_> = self.rev_operators.iter().take(i).cloned().collect(); -// let mut head = Operator::new_children(simplex, *offset); -// let indices = simplex.swap_edges_children_map(); -// let take = Operator::new_take(indices, simplex.nchildren() * simplex.nedges()); -// rev_tail.push(take); -// rev_tail.push(op.clone()); -// for op in self.rev_operators.iter().skip(i + 1) { -// if let Some(ops) = swap(op, &mut head) { -// rev_tail.extend(ops.into_iter().rev()); -// } else { -// continue 'b; -// } -// } -// heads.insert(head, rev_tail); -// } -// } -// heads -// } -// /// Remove and return the common prefix of two chains, transforming either if necessary. -// pub fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { -// let mut common = Vec::new(); -// let mut rev_a = self.rev_operators.clone(); -// let mut rev_b = other.rev_operators.clone(); -// let mut i = 0; -// while !rev_a.is_empty() && !rev_b.is_empty() { -// i += 1; -// if i > 10 { -// break; -// } -// if rev_a.last() == rev_b.last() { -// common.push(rev_a.pop().unwrap()); -// rev_b.pop(); -// continue; -// } -// let heads_a = UnsizedChain::new(rev_a.iter().rev().cloned()).split_heads(); -// let heads_b = UnsizedChain::new(rev_b.iter().rev().cloned()).split_heads(); -// if let Some((head, a, b)) = heads_a -// .iter() -// .filter_map(|(h, a)| heads_b.get(h).map(|b| (h, a, b))) -// .min_by_key(|(_, a, b)| std::cmp::max(a.len(), b.len())) -// { -// common.push(head.clone()); -// rev_a = a.clone(); -// rev_b = b.clone(); -// continue; -// } -// break; -// } -// let common = if rev_a.is_empty() -// && (!rev_b.is_empty() || self.rev_operators.len() <= other.rev_operators.len()) -// { -// self.clone() -// } else if rev_b.is_empty() { -// other.clone() -// } else { -// Self::new(common) -// }; -// ( -// common, -// Self::new(rev_a.into_iter().rev()), -// Self::new(rev_b.into_iter().rev()), -// ) -// } -//} -// -//impl UnsizedSequence for UnsizedChain { -// #[inline] -// fn delta_dim(&self) -> usize { -// self.rev_operators.iter().map(|op| op.delta_dim()).sum() -// } -// #[inline] -// fn delta_len(&self) -> (usize, usize) { -// self.rev_operators.iter().rfold((1, 1), |(n, d), op| { -// let (opn, opd) = op.delta_len(); -// (n * opn, d * opd) -// }) -// } -// #[inline] -// fn increment_offset(&mut self, amount: usize) { -// for op in self.rev_operators.iter_mut() { -// op.increment_offset(amount); -// } -// } -// #[inline] -// fn decrement_offset(&mut self, amount: usize) { -// for op in self.rev_operators.iter_mut() { -// op.decrement_offset(amount); -// } -// } -// #[inline] -// fn increment_stride(&mut self, amount: usize) { -// for op in self.rev_operators.iter_mut() { -// op.increment_stride(amount); -// } -// } -// #[inline] -// fn decrement_stride(&mut self, amount: usize) { -// for op in self.rev_operators.iter_mut() { -// op.decrement_stride(amount); -// } -// } -// #[inline] -// fn apply_index(&self, index: usize) -> usize { -// self.rev_operators -// .iter() -// .fold(index, |index, op| op.apply_index(index)) -// } -// #[inline] -// fn unapply_indices(&self, indices: &[usize]) -> Vec { -// let indices = indices.iter().cloned().collect(); -// self.rev_operators -// .iter() -// .rev() -// .fold(indices, |indices, op| op.unapply_indices(&indices)) -// } -// #[inline] -// fn apply_one_inplace(&self, index: usize, coordinate: &mut [f64]) -> usize { -// self.rev_operators -// .iter() -// .fold(index, |index, op| op.apply_one_inplace(index, coordinate)) -// } -// #[inline] -// fn apply_many_inplace(&self, index: usize, coordinates: &mut [f64], dim: usize) -> usize { -// self.rev_operators.iter().fold(index, |index, op| { -// op.apply_many_inplace(index, coordinates, dim) -// }) -// } -//} -// -//impl IntoIterator for UnsizedChain { -// type Item = Operator; -// type IntoIter = std::vec::IntoIter; -// -// fn into_iter(self) -> Self::IntoIter { -// self.rev_operators.into_iter() -// } -//} -// -//impl FromIterator for UnsizedChain { -// fn from_iter(iter: T) -> Self -// where -// T: IntoIterator, -// { -// let ops: Vec<_> = iter.into_iter().collect(); -// UnsizedChain::new(ops) -// } -//} -// -//#[derive(Debug, Clone)] -//pub struct Topology { -// transforms: UnsizedChain, -// dim: usize, -// root_len: usize, -// len: usize, -//} -// -//impl Topology { -// pub fn new(dim: usize, len: usize) -> Self { -// Self { -// transforms: UnsizedChain::new([]), -// dim, -// root_len: len, -// len, -// } -// } -// pub fn derive(&self, operator: Operator) -> Self { -// let (n, d) = operator.delta_len(); -// Self { -// root_len: self.root_len, -// len: self.len * n / d, -// dim: self.dim - operator.delta_dim(), -// transforms: self.transforms.clone_and_push(operator), -// } -// } -//} -// -//impl Mul for &Topology { -// type Output = Topology; -// -// fn mul(self, other: &Topology) -> Topology { -// Topology { -// transforms: UnsizedChain::new( -// iter::once(Operator::new_transpose(other.root_len, self.root_len)) -// .chain(self.transforms.iter().cloned()) -// .chain(iter::once(Operator::new_transpose( -// self.len, -// other.root_len, -// ))) -// .chain(other.transforms.iter().map(|op| { -// let mut op = op.clone(); -// op.increment_offset(self.dim); -// op -// })), -// ), -// dim: self.dim + other.dim, -// root_len: self.root_len * other.root_len, -// len: self.len * other.len, -// } -// } -//} -// -// -// -// -// macro_rules! assert_eq_op_apply { -// ($op:expr, $ii:expr, $ic:expr, $oi:expr, $oc:expr) => {{ -// let ic = $ic; -// let oc = $oc; -// let mut work = oc.clone(); -// for i in 0..ic.len() { -// work[i] = ic[i]; -// } -// for i in ic.len()..oc.len() { -// work[i] = 0.0; -// } -// assert_eq!($op.apply_one_inplace($ii, &mut work), $oi); -// assert_abs_diff_eq!(work[..], oc[..]); -// }}; -// } -// -// #[test] -// fn apply_children_line() { -// let op = Operator::new_children(Line, 0); -// assert_eq_op_apply!(op, 0 * 2 + 0, [0.0], 0, [0.0]); -// assert_eq_op_apply!(op, 1 * 2 + 0, [1.0], 1, [0.5]); -// assert_eq_op_apply!(op, 2 * 2 + 1, [0.0], 2, [0.5]); -// assert_eq_op_apply!(op, 3 * 2 + 1, [1.0], 3, [1.0]); -// assert_eq_op_apply!(op, 0, [0.0, 2.0], 0, [0.0, 2.0]); -// assert_eq_op_apply!(op, 1, [0.0, 3.0, 4.0], 0, [0.5, 3.0, 4.0]); -// let op = Operator::new_children(Line, 1); -// assert_eq_op_apply!(op, 1, [2.0, 0.0], 0, [2.0, 0.5]); -// assert_eq_op_apply!(op, 1, [3.0, 0.0, 4.0], 0, [3.0, 0.5, 4.0]); -// } -// -// #[test] -// fn apply_edges_line() { -// let op = Operator::new_edges(Line, 0); -// assert_eq_op_apply!(op, 0, [], 0, [1.0]); -// assert_eq_op_apply!(op, 3, [], 1, [0.0]); -// assert_eq_op_apply!(op, 4, [], 2, [1.0]); -// assert_eq_op_apply!(op, 7, [], 3, [0.0]); -// assert_eq_op_apply!(op, 0, [2.0], 0, [1.0, 2.0]); -// assert_eq_op_apply!(op, 1, [3.0, 4.0], 0, [0.0, 3.0, 4.0]); -// let op = Operator::new_edges(Line, 1); -// assert_eq_op_apply!(op, 0, [2.0], 0, [2.0, 1.0]); -// assert_eq_op_apply!(op, 0, [3.0, 4.0], 0, [3.0, 1.0, 4.0]); -// } -// -// // #[test] -// // fn apply_edges_square() { -// // let op = Operator::Edges { -// // simplices: Box::new([Line, Line]), -// // offset: 0, -// // }; -// // assert_eq!(op.apply(0 * 4 + 0, &[0.0]), (0, vec![1.0, 0.0])); -// // assert_eq!(op.apply(1 * 4 + 0, &[1.0]), (1, vec![1.0, 1.0])); -// // assert_eq!(op.apply(2 * 4 + 1, &[0.0]), (2, vec![0.0, 0.0])); -// // assert_eq!(op.apply(3 * 4 + 1, &[1.0]), (3, vec![0.0, 1.0])); -// // assert_eq!(op.apply(4 * 4 + 2, &[0.0]), (4, vec![0.0, 1.0])); -// // assert_eq!(op.apply(5 * 4 + 2, &[1.0]), (5, vec![1.0, 1.0])); -// // assert_eq!(op.apply(6 * 4 + 3, &[0.0]), (6, vec![0.0, 0.0])); -// // assert_eq!(op.apply(7 * 4 + 3, &[1.0]), (7, vec![1.0, 0.0])); -// // assert_eq!(op.apply(0, &[0.0, 2.0]), (0, vec![1.0, 0.0, 2.0])); -// // assert_eq!(op.apply(1, &[0.0, 3.0, 4.0]), (0, vec![0.0, 0.0, 3.0, 4.0])); -// // } -// -// #[test] -// fn apply_transpose_index() { -// let op = Operator::new_transpose(2, 3); -// for i in 0..3 { -// for j in 0..2 { -// for k in 0..3 { -// assert_eq!( -// op.apply_one((i * 2 + j) * 3 + k, &[]), -// ((i * 3 + k) * 2 + j, vec![]) -// ); -// } -// } -// } -// } -// -// #[test] -// fn apply_take_all() { -// let op = Operator::new_take([3, 2, 0, 4, 1], 5); // inverse: [2, 4, 1, 0, 3] -// assert_eq_op_apply!(op, 0, [], 3, []); -// assert_eq_op_apply!(op, 6, [1.0], 7, [1.0]); -// assert_eq_op_apply!(op, 12, [2.0, 3.0], 10, [2.0, 3.0]); -// assert_eq_op_apply!(op, 18, [], 19, []); -// assert_eq_op_apply!(op, 24, [], 21, []); -// } -// -// #[test] -// fn apply_take_some() { -// let op = Operator::new_take([4, 0, 1], 5); // inverse: [1, 2, x, x, 0] -// assert_eq_op_apply!(op, 0, [], 4, []); -// assert_eq_op_apply!(op, 4, [1.0], 5, [1.0]); -// assert_eq_op_apply!(op, 8, [2.0, 3.0], 11, [2.0, 3.0]); -// } -// -// #[test] -// fn apply_uniform_points() { -// let op = Operator::new_uniform_points(Box::new([0.0, 1.0, 2.0, 3.0, 4.0, 5.0]), 2, 0); -// assert_eq_op_apply!(op, 0, [], 0, [0.0, 1.0]); -// assert_eq_op_apply!(op, 4, [6.0], 1, [2.0, 3.0, 6.0]); -// assert_eq_op_apply!(op, 8, [7.0, 8.0], 2, [4.0, 5.0, 7.0, 8.0]); -// } -// -// #[test] -// fn mul_topo() { -// let xtopo = Topology::new(1, 2).derive(Operator::new_children(Line, 0)); -// let ytopo = Topology::new(1, 3).derive(Operator::new_children(Line, 0)); -// let xytopo = &xtopo * &ytopo; -// assert_eq!(xtopo.len, 4); -// assert_eq!(ytopo.len, 6); -// assert_eq!(xytopo.len, 24); -// assert_eq!(xytopo.root_len, 6); -// for i in 0..4 { -// for j in 0..6 { -// let x = xtopo.transforms.apply_many(i, &[0.0, 0.0, 1.0, 1.0], 1).1; -// let y = ytopo.transforms.apply_many(j, &[0.0, 1.0, 0.0, 1.0], 1).1; -// let mut xy = Vec::with_capacity(8); -// for k in 0..4 { -// xy.push(x[k]); -// xy.push(y[k]); -// } -// assert_eq!( -// xytopo.transforms.apply_many( -// i * 6 + j, -// &[0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0], -// 2 -// ), -// ((i / 2) * 3 + j / 2, xy), -// ); -// } -// } -// } -// -// macro_rules! assert_equiv_topo { -// ($topo1:expr, $topo2:expr$(, $simplex:ident)*) => { -// #[allow(unused_mut)] -// let mut topo1 = $topo1.clone(); -// #[allow(unused_mut)] -// let mut topo2 = $topo2.clone(); -// assert_eq!(topo1.dim, topo2.dim, "topos have different dim"); -// assert_eq!(topo1.len, topo2.len, "topos have different len"); -// assert_eq!(topo1.root_len, topo2.root_len, "topos have different root_len"); -// let from_dim = 0$(+$simplex.dim())*; -// assert_eq!(topo1.dim, from_dim, "dimension of topo differs from dimension of given simplices"); -// let nelems = topo1.len; -// $( -// let points = Operator::new_uniform_points( -// $simplex.vertices().into(), -// $simplex.dim(), -// 0, -// ); -// topo1 = topo1.derive(points.clone()); -// topo2 = topo2.derive(points.clone()); -// )* -// let npoints = topo1.len; -// let mut coord1: Vec<_> = iter::repeat(0.0).take((topo1.dim + topo1.transforms.delta_dim()) as usize).collect(); -// let mut coord2 = coord1.clone(); -// for i in 0..topo1.len { -// let ielem = i / (npoints / nelems); -// assert_eq!( -// topo1.transforms.apply_one_inplace(i, &mut coord1), -// topo2.transforms.apply_one_inplace(i, &mut coord2), -// "topo1 and topo2 map element {ielem} to different root elements" -// ); -// assert_abs_diff_eq!(coord1[..], coord2[..]); -// } -// }; -// } -// -// #[test] -// fn swap_edges_children_1d() { -// let topo1 = Topology::new(1, 3).derive(Operator::new_edges(Line, 0)); -// let topo2 = Topology::new(1, 3) -// .derive(Operator::new_children(Line, 0)) -// .derive(Operator::new_edges(Line, 0)) -// .derive(Operator::new_take([2, 1], 4)); -// assert_equiv_topo!(topo1, topo2); -// } -// -// #[test] -// fn swap_take_children() { -// let take = Operator::new_take([2, 3, 1], 5); -// let children = Operator::new_children(Line, 0); -// let swapped = vec![ -// children.clone(), -// Operator::new_transpose(2, 5), -// take.clone(), -// Operator::new_transpose(3, 2), -// ]; -// let base = Topology::new(1, 5); -// assert_eq!(take.swap(&children), Some(swapped.clone())); -// assert_equiv_topo!( -// base.derive(take).derive(children), -// swapped -// .iter() -// .cloned() -// .fold(base.clone(), |t, o| t.derive(o)), -// Line -// ); -// } -// -// #[test] -// fn swap_take_edges() { -// let take = Operator::new_take([2, 3, 1], 5); -// let edges = Operator::new_edges(Line, 0); -// let swapped = vec![ -// edges.clone(), -// Operator::new_transpose(2, 5), -// take.clone(), -// Operator::new_transpose(3, 2), -// ]; -// let base = Topology::new(1, 5); -// assert_eq!(take.swap(&edges), Some(swapped.clone())); -// assert_equiv_topo!( -// base.derive(take).derive(edges), -// swapped -// .iter() -// .cloned() -// .fold(base.clone(), |t, o| t.derive(o)) -// ); -// } -// -// macro_rules! fn_test_operator_swap { -// ($name:ident, $len:expr $(, $simplex:ident)*; $op1:expr, $op2:expr,) => { -// #[test] -// fn $name() { -// let op1: Operator = $op1; -// let op2: Operator = $op2; -// let swapped = op1.swap(&op2).expect("not swapped"); -// println!("op1: {op1:?}"); -// println!("op2: {op2:?}"); -// println!("swapped: {swapped:?}"); -// let root_dim = op1.delta_dim() + op2.delta_dim() $(+ $simplex.dim())*; -// let base = Topology::new(root_dim, 1); -// let topo1 = [op1, op2].iter().fold(base.clone(), |t, o| t.derive(o.clone())); -// let topo2 = swapped.iter().fold(base, |t, o| t.derive(o.clone())); -// let len = $len; -// assert_eq!(topo1.len, len, "unswapped topo has unexpected length"); -// assert_eq!(topo2.len, len, "swapped topo has unexpected length"); -// assert_equiv_topo!(topo1, topo2 $(, $simplex)*); -// } -// } -// } -// -// fn_test_operator_swap! { -// swap_edges_children_triangle1, 6, Line, Line; -// Operator::new_edges(Triangle, 0), -// Operator::new_children(Line, 0), -// } -// -// fn_test_operator_swap! { -// swap_unoverlapping_children_lt_children, 8, Triangle, Line; -// Operator::new_children(Triangle, 0), -// Operator::new_children(Line, 2), -// } -// -// fn_test_operator_swap! { -// swap_unoverlapping_children_gt_children, 8, Line, Triangle; -// Operator::new_children(Line, 2), -// Operator::new_children(Triangle, 0), -// } -// -// fn_test_operator_swap! { -// swap_unoverlapping_edges_lt_children, 6, Line, Line; -// Operator::new_edges(Triangle, 0), -// Operator::new_children(Line, 1), -// } -// -// fn_test_operator_swap! { -// swap_unoverlapping_edges_gt_children, 6, Line, Line; -// Operator::new_edges(Triangle, 1), -// Operator::new_children(Line, 0), -// } -// -// fn_test_operator_swap! { -// swap_unoverlapping_children_lt_edges, 6, Line, Line; -// Operator::new_children(Line, 0), -// Operator::new_edges(Triangle, 1), -// } -// -// fn_test_operator_swap! { -// swap_unoverlapping_children_gt_edges, 6, Line, Line; -// Operator::new_children(Line, 2), -// Operator::new_edges(Triangle, 0), -// } -// -// fn_test_operator_swap! { -// swap_unoverlapping_edges_lt_edges, 6, Line; -// Operator::new_edges(Line, 0), -// Operator::new_edges(Triangle, 0), -// } -// -// fn_test_operator_swap! { -// swap_unoverlapping_edges_gt_edges, 6, Line; -// Operator::new_edges(Line, 2), -// Operator::new_edges(Triangle, 0), -// } -// -// #[test] -// fn split_heads() { -// let chain = UnsizedChain::new([ -// Operator::new_edges(Triangle, 1), -// Operator::new_children(Line, 0), -// Operator::new_edges(Line, 2), -// Operator::new_children(Line, 1), -// Operator::new_children(Line, 0), -// ]); -// let desired = chain -// .iter() -// .cloned() -// .fold(Topology::new(4, 1), |topo, op| topo.derive(op)); -// for (head, tail) in chain.split_heads().into_iter() { -// let actual = iter::once(head) -// .chain(tail.into_iter().rev()) -// .fold(Topology::new(4, 1), |topo, op| topo.derive(op)); -// assert_equiv_topo!(actual, desired, Line, Line); -// } -// } -// - #[cfg(test)] mod tests { use super::*; @@ -810,17 +173,17 @@ mod tests { #[test] fn remove_common_prefix() { - let c1 = Operator::new_children(Line); - let e1 = Operator::new_edges(Line); - let swap_ec1 = Operator::new_take([2, 1], 4); - let a = UnsizedChain::new(vec![c1.clone(), c1.clone()]); - let b = UnsizedChain::new(vec![e1.clone()]); + let c1 = Elementary::new_children(Line); + let e1 = Elementary::new_edges(Line); + let swap_ec1 = Elementary::new_take([2, 1], 4); + let a = vec![c1.clone(), c1.clone()]; + let b = vec![e1.clone()]; assert_eq!( a.remove_common_prefix(&b), ( - UnsizedChain::new(vec![c1.clone(), c1.clone()]), - UnsizedChain::identity(), - UnsizedChain::new(vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()]), + vec![c1.clone(), c1.clone()], + vec![], + vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], ) ); } diff --git a/src/operator.rs b/src/elementary.rs similarity index 65% rename from src/operator.rs rename to src/elementary.rs index efed34131..d04c69e8d 100644 --- a/src/operator.rs +++ b/src/elementary.rs @@ -1,8 +1,8 @@ use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; +use crate::UnsizedMapping; use num::Integer; use std::rc::Rc; -use crate::UnsizedMapping; #[inline] const fn divmod(x: usize, y: usize) -> (usize, usize) { @@ -58,7 +58,7 @@ impl UnsizedMapping for Transpose { fn mod_out(&self) -> usize { self.mod_in() } - fn apply_inplace(&self, index: usize, _coordinates: &mut[f64], _stride: usize) -> usize { + fn apply_inplace(&self, index: usize, _coordinates: &mut [f64], _stride: usize) -> usize { self.apply_index(index) } fn apply_index(&self, index: usize) -> usize { @@ -111,7 +111,7 @@ impl UnsizedMapping for Take { fn mod_out(&self) -> usize { self.len } - fn apply_inplace(&self, index: usize, _coordinates: &mut[f64], _stride: usize) -> usize { + fn apply_inplace(&self, index: usize, _coordinates: &mut [f64], _stride: usize) -> usize { self.apply_index(index) } fn apply_index(&self, index: usize) -> usize { @@ -157,14 +157,14 @@ impl UnsizedMapping for Children { fn mod_out(&self) -> usize { 1 } - fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize { + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { self.0.apply_child(index, coordinates, stride, self.1) } fn apply_index(&self, index: usize) -> usize { self.0.apply_child_index(index) } fn unapply_indices(&self, indices: &[usize]) -> Vec { - indices + indices .iter() .map(|i| i * self.mod_in()) .flat_map(|i| (0..self.mod_in()).map(move |j| i + j)) @@ -197,14 +197,14 @@ impl UnsizedMapping for Edges { fn mod_out(&self) -> usize { 1 } - fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize { + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { self.0.apply_edge(index, coordinates, stride, self.1) } fn apply_index(&self, index: usize) -> usize { self.0.apply_edge_index(index) } fn unapply_indices(&self, indices: &[usize]) -> Vec { - indices + indices .iter() .map(|i| i * self.mod_in()) .flat_map(|i| (0..self.mod_in()).map(move |j| i + j)) @@ -251,7 +251,7 @@ impl UnsizedMapping for UniformPoints { fn mod_out(&self) -> usize { 1 } - fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize { + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { let points: &[f64] = unsafe { std::mem::transmute(&self.points[..]) }; let point = &points[(index % self.npoints) * self.point_dim..][..self.point_dim]; for coord in coordinates_iter_mut(coordinates, stride, self.offset, self.point_dim, 0) { @@ -263,7 +263,7 @@ impl UnsizedMapping for UniformPoints { index / self.npoints } fn unapply_indices(&self, indices: &[usize]) -> Vec { - indices + indices .iter() .map(|i| i * self.mod_in()) .flat_map(|i| (0..self.mod_in()).map(move |j| i + j)) @@ -272,7 +272,7 @@ impl UnsizedMapping for UniformPoints { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Operator { +pub enum Elementary { Transpose(Transpose), Take(Take), Children(Children), @@ -280,7 +280,7 @@ pub enum Operator { UniformPoints(UniformPoints), } -impl Operator { +impl Elementary { #[inline] pub fn new_transpose(len1: usize, len2: usize) -> Self { Self::Transpose(Transpose::new(len1, len2)) @@ -319,29 +319,32 @@ impl Operator { self.set_offset(new_offset); self } - pub fn shift_left(&self, operators: &[Self]) -> Option<(Option, Self, Vec)> { + pub fn shift_left(&self, items: &[Self]) -> Option<(Option, Self, Vec)> { if self.is_transpose() { return None; } let mut target = self.clone(); - let mut shifted_ops: Vec = Vec::new(); + let mut shifted_items: Vec = Vec::new(); let mut queue: Vec = Vec::new(); let mut stride_out = 1; let mut stride_in = 1; - for mut op in operators.iter().rev().cloned() { + for mut item in items.iter().rev().cloned() { // Swap matching edges and children at the same offset. - if let Self::Edges(Edges(esimplex, eoffset)) = &op { + if let Self::Edges(Edges(esimplex, eoffset)) = &item { if let Self::Children(Children(ref mut csimplex, coffset)) = &mut target { if eoffset == coffset && esimplex.edge_dim() == csimplex.dim() { if stride_in != 1 && self.mod_in() != 1 { - shifted_ops.push(Self::new_transpose(stride_in, self.mod_in())); + shifted_items.push(Self::new_transpose(stride_in, self.mod_in())); } - shifted_ops.append(&mut queue); + shifted_items.append(&mut queue); if stride_out != 1 && self.mod_in() != 1 { - shifted_ops.push(Self::new_transpose(self.mod_in(), stride_out)); + shifted_items.push(Self::new_transpose(self.mod_in(), stride_out)); } - shifted_ops.push(Self::new_take(esimplex.swap_edges_children_map(), esimplex.nedges() * esimplex.nchildren())); - shifted_ops.push(Self::Edges(Edges(*esimplex, *eoffset))); + shifted_items.push(Self::new_take( + esimplex.swap_edges_children_map(), + esimplex.nedges() * esimplex.nchildren(), + )); + shifted_items.push(Self::Edges(Edges(*esimplex, *eoffset))); *csimplex = *esimplex; stride_in = 1; stride_out = 1; @@ -352,10 +355,10 @@ impl Operator { // Update strides. if self.mod_in() == 1 && self.mod_out() == 1 { } else if self.mod_out() == 1 { - let n = stride_out.gcd(&op.mod_in()); - stride_out = stride_out / n * op.mod_out(); - stride_in *= op.mod_in() / n; - } else if let Some(Transpose(ref mut m, ref mut n)) = op.as_transpose_mut() { + let n = stride_out.gcd(&item.mod_in()); + stride_out = stride_out / n * item.mod_out(); + stride_in *= item.mod_in() / n; + } else if let Some(Transpose(ref mut m, ref mut n)) = item.as_transpose_mut() { if stride_out % (*m * *n) == 0 { } else if stride_out % *n == 0 && (*m * *n) % (stride_out * self.mod_out()) == 0 { stride_out /= *n; @@ -366,47 +369,45 @@ impl Operator { } else { return None; } - } else if stride_out % op.mod_in() == 0 { - stride_out = stride_out / op.mod_in() * op.mod_out(); + } else if stride_out % item.mod_in() == 0 { + stride_out = stride_out / item.mod_in() * item.mod_out(); } else { return None; } // Update offsets. - let op_delta_dim = op.delta_dim(); + let item_delta_dim = item.delta_dim(); let target_delta_dim = target.delta_dim(); - let op_dim_in = op.dim_in(); + let item_dim_in = item.dim_in(); let target_dim_out = target.dim_out(); - if let (Some(op_offset), Some(target_offset)) = (op.offset_mut(), target.offset_mut()) { - if op_dim_in <= *target_offset { - *target_offset += op_delta_dim; - } else if target_dim_out <= *op_offset { - *op_offset -= target_delta_dim; + if let (Some(item_offset), Some(target_offset)) = + (item.offset_mut(), target.offset_mut()) + { + if item_dim_in <= *target_offset { + *target_offset += item_delta_dim; + } else if target_dim_out <= *item_offset { + *item_offset -= target_delta_dim; } else { return None; } } - if !op.is_identity() { - queue.push(op); + if !item.is_identity() { + queue.push(item); } } if stride_in != 1 && self.mod_in() != 1 { - shifted_ops.push(Self::new_transpose(stride_in, self.mod_in())); + shifted_items.push(Self::new_transpose(stride_in, self.mod_in())); } - shifted_ops.extend(queue); + shifted_items.extend(queue); if stride_out != 1 && self.mod_in() != 1 { - shifted_ops.push(Self::new_transpose(self.mod_in(), stride_out)); + shifted_items.push(Self::new_transpose(self.mod_in(), stride_out)); } let leading_transpose = if self.mod_out() == 1 || stride_out == 1 { None } else { Some(Transpose::new(stride_out, self.mod_out())) }; - shifted_ops.reverse(); - Some(( - leading_transpose, - target, - shifted_ops, - )) + shifted_items.reverse(); + Some((leading_transpose, target, shifted_items)) } #[inline] const fn is_transpose(&self) -> bool { @@ -431,11 +432,11 @@ macro_rules! dispatch { #[inline] $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { match $self { - Operator::Transpose(var) => var.$fn($($arg),*), - Operator::Take(var) => var.$fn($($arg),*), - Operator::Children(var) => var.$fn($($arg),*), - Operator::Edges(var) => var.$fn($($arg),*), - Operator::UniformPoints(var) => var.$fn($($arg),*), + Elementary::Transpose(var) => var.$fn($($arg),*), + Elementary::Take(var) => var.$fn($($arg),*), + Elementary::Children(var) => var.$fn($($arg),*), + Elementary::Edges(var) => var.$fn($($arg),*), + Elementary::UniformPoints(var) => var.$fn($($arg),*), } } }; @@ -443,54 +444,54 @@ macro_rules! dispatch { #[inline] $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { match $self { - Operator::Transpose(var) => var.$fn($($arg),*), - Operator::Take(var) => var.$fn($($arg),*), - Operator::Children(var) => var.$fn($($arg),*), - Operator::Edges(var) => var.$fn($($arg),*), - Operator::UniformPoints(var) => var.$fn($($arg),*), + Elementary::Transpose(var) => var.$fn($($arg),*), + Elementary::Take(var) => var.$fn($($arg),*), + Elementary::Children(var) => var.$fn($($arg),*), + Elementary::Edges(var) => var.$fn($($arg),*), + Elementary::UniformPoints(var) => var.$fn($($arg),*), } } }; } -impl UnsizedMapping for Operator { - dispatch!{fn dim_in(&self) -> usize} - dispatch!{fn delta_dim(&self) -> usize} - dispatch!{fn add_offset(&mut self, offset: usize)} - dispatch!{fn mod_in(&self) -> usize} - dispatch!{fn mod_out(&self) -> usize} - dispatch!{fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize} - dispatch!{fn apply_index(&self, index: usize) -> usize} - dispatch!{fn apply_indices_inplace(&self, indices: &mut [usize])} - dispatch!{fn unapply_indices(&self, indices: &[usize]) -> Vec} - dispatch!{fn is_identity(&self) -> bool} +impl UnsizedMapping for Elementary { + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn add_offset(&mut self, offset: usize)} + dispatch! {fn mod_in(&self) -> usize} + dispatch! {fn mod_out(&self) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> usize} + dispatch! {fn apply_indices_inplace(&self, indices: &mut [usize])} + dispatch! {fn unapply_indices(&self, indices: &[usize]) -> Vec} + dispatch! {fn is_identity(&self) -> bool} } -impl From for Operator { +impl From for Elementary { fn from(transpose: Transpose) -> Self { Self::Transpose(transpose) } } -impl From for Operator { +impl From for Elementary { fn from(take: Take) -> Self { Self::Take(take) } } -impl From for Operator { +impl From for Elementary { fn from(children: Children) -> Self { Self::Children(children) } } -impl From for Operator { +impl From for Elementary { fn from(edges: Edges) -> Self { Self::Edges(edges) } } -impl From for Operator { +impl From for Elementary { fn from(uniform_points: UniformPoints) -> Self { Self::UniformPoints(uniform_points) } @@ -504,16 +505,16 @@ mod tests { use Simplex::*; macro_rules! assert_eq_apply { - ($op:expr, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ + ($item:expr, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ use std::borrow::Borrow; - let op = $op.borrow(); + let item = $item.borrow(); let incoords = $incoords; let outcoords = $outcoords; assert_eq!(incoords.len(), outcoords.len()); let stride; let mut work: Vec<_>; if incoords.len() == 0 { - stride = op.dim_out(); + stride = item.dim_out(); work = Vec::with_capacity(0); } else { stride = outcoords[0].len(); @@ -522,90 +523,99 @@ mod tests { work[..incoord.len()].copy_from_slice(incoord); } } - assert_eq!(op.apply_inplace($inidx, &mut work, stride), $outidx); + assert_eq!(item.apply_inplace($inidx, &mut work, stride), $outidx); for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { assert_abs_diff_eq!(actual[..], desired[..]); } }}; - ($op:expr, $inidx:expr, $outidx:expr) => {{ + ($item:expr, $inidx:expr, $outidx:expr) => {{ use std::borrow::Borrow; - let op = $op.borrow(); + let item = $item.borrow(); let mut work = Vec::with_capacity(0); - assert_eq!(op.apply_inplace($inidx, &mut work, op.dim_out()), $outidx); + assert_eq!( + item.apply_inplace($inidx, &mut work, item.dim_out()), + $outidx + ); }}; } #[test] fn apply_transpose() { - let op = Operator::new_transpose(3, 2); - assert_eq_apply!(op, 0, 0); - assert_eq_apply!(op, 1, 3); - assert_eq_apply!(op, 2, 1); - assert_eq_apply!(op, 3, 4); - assert_eq_apply!(op, 4, 2); - assert_eq_apply!(op, 5, 5); - assert_eq_apply!(op, 6, 6); - assert_eq_apply!(op, 7, 9); + let item = Elementary::new_transpose(3, 2); + assert_eq_apply!(item, 0, 0); + assert_eq_apply!(item, 1, 3); + assert_eq_apply!(item, 2, 1); + assert_eq_apply!(item, 3, 4); + assert_eq_apply!(item, 4, 2); + assert_eq_apply!(item, 5, 5); + assert_eq_apply!(item, 6, 6); + assert_eq_apply!(item, 7, 9); } #[test] fn apply_take() { - let op = Operator::new_take([4, 1, 2], 5); - assert_eq_apply!(op, 0, 4); - assert_eq_apply!(op, 1, 1); - assert_eq_apply!(op, 2, 2); - assert_eq_apply!(op, 3, 9); - assert_eq_apply!(op, 4, 6); - assert_eq_apply!(op, 5, 7); + let item = Elementary::new_take([4, 1, 2], 5); + assert_eq_apply!(item, 0, 4); + assert_eq_apply!(item, 1, 1); + assert_eq_apply!(item, 2, 2); + assert_eq_apply!(item, 3, 9); + assert_eq_apply!(item, 4, 6); + assert_eq_apply!(item, 5, 7); } #[test] fn apply_children_line() { - let op = Operator::new_children(Line); - assert_eq_apply!(op, 0, [[0.0], [1.0]], 0, [[0.0], [0.5]]); - assert_eq_apply!(op, 1, [[0.0], [1.0]], 0, [[0.5], [1.0]]); - assert_eq_apply!(op, 2, [[0.0], [1.0]], 1, [[0.0], [0.5]]); - - let op = op.with_offset(1); - assert_eq_apply!(op, 3, [[0.2, 0.0], [0.3, 1.0]], 1, [[0.2, 0.5], [0.3, 1.0]]); + let item = Elementary::new_children(Line); + assert_eq_apply!(item, 0, [[0.0], [1.0]], 0, [[0.0], [0.5]]); + assert_eq_apply!(item, 1, [[0.0], [1.0]], 0, [[0.5], [1.0]]); + assert_eq_apply!(item, 2, [[0.0], [1.0]], 1, [[0.0], [0.5]]); + + let item = item.with_offset(1); + assert_eq_apply!( + item, + 3, + [[0.2, 0.0], [0.3, 1.0]], + 1, + [[0.2, 0.5], [0.3, 1.0]] + ); } #[test] fn apply_edges_line() { - let op = Operator::new_edges(Line); - assert_eq_apply!(op, 0, [[]], 0, [[1.0]]); - assert_eq_apply!(op, 1, [[]], 0, [[0.0]]); - assert_eq_apply!(op, 2, [[]], 1, [[1.0]]); + let item = Elementary::new_edges(Line); + assert_eq_apply!(item, 0, [[]], 0, [[1.0]]); + assert_eq_apply!(item, 1, [[]], 0, [[0.0]]); + assert_eq_apply!(item, 2, [[]], 1, [[1.0]]); - let op = op.with_offset(1); - assert_eq_apply!(op, 0, [[0.2]], 0, [[0.2, 1.0]]); + let item = item.with_offset(1); + assert_eq_apply!(item, 0, [[0.2]], 0, [[0.2, 1.0]]); } #[test] fn apply_uniform_points() { - let op = Operator::new_uniform_points([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); - assert_eq_apply!(op, 0, [[]], 0, [[1.0, 2.0]]); - assert_eq_apply!(op, 1, [[]], 0, [[3.0, 4.0]]); - assert_eq_apply!(op, 2, [[]], 0, [[5.0, 6.0]]); - assert_eq_apply!(op, 3, [[]], 1, [[1.0, 2.0]]); + let item = Elementary::new_uniform_points([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); + assert_eq_apply!(item, 0, [[]], 0, [[1.0, 2.0]]); + assert_eq_apply!(item, 1, [[]], 0, [[3.0, 4.0]]); + assert_eq_apply!(item, 2, [[]], 0, [[5.0, 6.0]]); + assert_eq_apply!(item, 3, [[]], 1, [[1.0, 2.0]]); - let op = op.with_offset(1); - assert_eq_apply!(op, 0, [[7.0]], 0, [[7.0, 1.0, 2.0]]); + let item = item.with_offset(1); + assert_eq_apply!(item, 0, [[7.0]], 0, [[7.0, 1.0, 2.0]]); } macro_rules! assert_unapply { - ($op:expr) => {{ - let op = $op; - let nin = 2 * op.mod_in(); - let nout = 2 * op.mod_out(); + ($item:expr) => {{ + let item = $item; + let nin = 2 * item.mod_in(); + let nout = 2 * item.mod_out(); assert!(nout > 0); let mut map: Vec> = (0..nout).map(|_| Vec::new()).collect(); let mut work = Vec::with_capacity(0); for i in 0..nin { - map[op.apply_inplace(i, &mut work, op.dim_out())].push(i); + map[item.apply_inplace(i, &mut work, item.dim_out())].push(i); } for (j, desired) in map.into_iter().enumerate() { - let mut actual = op.unapply_indices(&[j]); + let mut actual = item.unapply_indices(&[j]); actual.sort(); assert_eq!(actual, desired); } @@ -614,27 +624,27 @@ mod tests { #[test] fn unapply_indices_transpose() { - assert_unapply!(Operator::new_transpose(3, 2)); + assert_unapply!(Elementary::new_transpose(3, 2)); } #[test] fn unapply_indices_take() { - assert_unapply!(Operator::new_take([4, 1], 5)); + assert_unapply!(Elementary::new_take([4, 1], 5)); } #[test] fn unapply_indices_children() { - assert_unapply!(Operator::new_children(Triangle)); + assert_unapply!(Elementary::new_children(Triangle)); } #[test] fn unapply_indices_edges() { - assert_unapply!(Operator::new_edges(Triangle)); + assert_unapply!(Elementary::new_edges(Triangle)); } #[test] fn unapply_indices_uniform_points() { - assert_unapply!(Operator::new_uniform_points( + assert_unapply!(Elementary::new_uniform_points( [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], 2 )); @@ -642,8 +652,8 @@ mod tests { macro_rules! assert_equiv_chains { ($a:expr, $b:expr $(, $simplex:ident)*) => {{ - let a: &[Operator] = &$a; - let b: &[Operator] = &$b; + let a: &[Elementary] = &$a; + let b: &[Elementary] = &$b; println!("a: {a:?}"); println!("b: {b:?}"); let tip_dim = 0 $(+ $simplex.dim())*; @@ -651,26 +661,26 @@ mod tests { let mut tip_len = 1; let mut root_len = 1; let mut root_dim = tip_dim; - for op in a.iter().rev() { + for item in a.iter().rev() { let i = (1..) .into_iter() - .find(|i| (root_len * i) % op.mod_in() == 0) + .find(|i| (root_len * i) % item.mod_in() == 0) .unwrap(); tip_len *= i; root_len *= i; - root_len = root_len / op.mod_in() * op.mod_out(); - assert!(op.dim_in() <= root_dim); - root_dim += op.delta_dim(); + root_len = root_len / item.mod_in() * item.mod_out(); + assert!(item.dim_in() <= root_dim); + root_dim += item.delta_dim(); } assert!(tip_len > 0); // Verify the length and the root dimension for sequence `b`. let mut root_len_b = tip_len; let mut root_dim_b = tip_dim; - for op in b.iter().rev() { - assert_eq!(root_len_b % op.mod_in(), 0); - root_len_b = root_len_b / op.mod_in() * op.mod_out(); - assert!(op.dim_in() <= root_dim_b); - root_dim_b += op.delta_dim(); + for item in b.iter().rev() { + assert_eq!(root_len_b % item.mod_in(), 0); + root_len_b = root_len_b / item.mod_in() * item.mod_out(); + assert!(item.dim_in() <= root_dim_b); + root_dim_b += item.delta_dim(); } assert_eq!(root_len_b, root_len); assert_eq!(root_dim_b, root_dim); @@ -691,8 +701,8 @@ mod tests { for itip in 0..2 * tip_len { let mut crds_a = coords.clone(); let mut crds_b = coords.clone(); - let iroot_a = a.iter().rev().fold(itip, |i, op| op.apply_inplace(i, &mut crds_a, root_dim)); - let iroot_b = b.iter().rev().fold(itip, |i, op| op.apply_inplace(i, &mut crds_b, root_dim)); + let iroot_a = a.iter().rev().fold(itip, |i, item| item.apply_inplace(i, &mut crds_a, root_dim)); + let iroot_b = b.iter().rev().fold(itip, |i, item| item.apply_inplace(i, &mut crds_b, root_dim)); assert_eq!(iroot_a, iroot_b, "itip={itip}"); assert_abs_diff_eq!(crds_a[..], crds_b[..]); } @@ -700,14 +710,14 @@ mod tests { } macro_rules! assert_shift_left { - ($($op:expr),*; $($simplex:ident),*) => {{ - let unshifted = [$(Operator::from($op),)*]; - let (ltrans, lop, lchain) = unshifted.last().unwrap().shift_left(&unshifted[..unshifted.len()-1]).unwrap(); - let mut shifted: Vec = Vec::new(); + ($($item:expr),*; $($simplex:ident),*) => {{ + let unshifted = [$(Elementary::from($item),)*]; + let (ltrans, litem, lchain) = unshifted.last().unwrap().shift_left(&unshifted[..unshifted.len()-1]).unwrap(); + let mut shifted: Vec = Vec::new(); if let Some(ltrans) = ltrans { shifted.push(ltrans.into()); } - shifted.push(lop); + shifted.push(litem); shifted.extend(lchain.into_iter()); assert_equiv_chains!(&shifted[..], &unshifted[..] $(, $simplex)*); }}; @@ -716,28 +726,28 @@ mod tests { #[test] fn shift_left() { assert_shift_left!( - Transpose::new(4, 3), Operator::new_take([0, 1], 3); + Transpose::new(4, 3), Elementary::new_take([0, 1], 3); ); assert_shift_left!( - Transpose::new(3, 5), Transpose::new(5, 4*3), Operator::new_take([0, 1], 3); + Transpose::new(3, 5), Transpose::new(5, 4*3), Elementary::new_take([0, 1], 3); ); assert_shift_left!( - Transpose::new(5, 4), Transpose::new(5*4, 3), Operator::new_take([0, 1], 3); + Transpose::new(5, 4), Transpose::new(5*4, 3), Elementary::new_take([0, 1], 3); ); assert_shift_left!( - Operator::new_children(Line).with_offset(1), Operator::new_children(Line); + Elementary::new_children(Line).with_offset(1), Elementary::new_children(Line); Line, Line ); assert_shift_left!( - Operator::new_edges(Line), Operator::new_children(Line); + Elementary::new_edges(Line), Elementary::new_children(Line); Line ); assert_shift_left!( - Operator::new_edges(Line).with_offset(1), Operator::new_children(Line); + Elementary::new_edges(Line).with_offset(1), Elementary::new_children(Line); Line ); assert_shift_left!( - Operator::new_edges(Triangle), Operator::new_children(Line); + Elementary::new_edges(Triangle), Elementary::new_children(Line); Line ); } diff --git a/src/lib.rs b/src/lib.rs index f4ad975c7..fffb876e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ pub mod chain; +pub mod elementary; mod finite_f64; -pub mod operator; -pub mod simplex; pub mod sequence; +pub mod simplex; pub trait Mapping { fn len_out(&self) -> usize; @@ -14,7 +14,7 @@ pub trait Mapping { fn delta_dim(&self) -> usize; fn add_offset(&mut self, offset: usize); fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize; - fn apply_inplace(&self, index: usize, coordinates: &mut[f64]) -> Option { + fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { if index < self.len_in() { Some(self.apply_inplace_unchecked(index, coordinates)) } else { @@ -71,7 +71,7 @@ pub trait UnsizedMapping { fn mod_in(&self) -> usize; // Modulus if the output index. fn mod_out(&self) -> usize; - fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize; + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize; fn apply_index(&self, index: usize) -> usize; fn apply_indices_inplace(&self, indices: &mut [usize]) { for index in indices.iter_mut() { diff --git a/src/sequence.rs b/src/sequence.rs index ccbeea4df..39c0ac580 100644 --- a/src/sequence.rs +++ b/src/sequence.rs @@ -1,8 +1,162 @@ -use crate::chain::UnsizedChain; -use crate::operator::Operator; +use crate::chain::RemoveCommonPrefix; +use crate::elementary::Elementary; use crate::{Mapping, UnsizedMapping}; use std::iter; -use std::ops::Mul; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum NewSizeError { + DimensionTooSmall, + LengthNotAMultipleOfRepetition, +} + +impl std::error::Error for NewSizeError {} + +impl std::fmt::Display for NewSizeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::DimensionTooSmall => write!(f, "The dimension of the sized mapping is smaller than the minimum dimension of the unsized mapping."), + Self::LengthNotAMultipleOfRepetition => write!(f, "The length of the sized mapping is not a multiple of the repetition length of the unsized mapping."), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Size { + mapping: M, + delta_dim: usize, + dim_in: usize, + len_out: usize, + len_in: usize, +} + +impl Size { + pub fn new(mapping: M, dim_out: usize, len_out: usize) -> Result { + if dim_out < mapping.dim_out() { + Err(NewSizeError::DimensionTooSmall) + } else if len_out % mapping.mod_out() != 0 { + Err(NewSizeError::LengthNotAMultipleOfRepetition) + } else { + let delta_dim = mapping.delta_dim(); + let dim_in = dim_out - delta_dim; + let len_in = len_out / mapping.mod_out() * mapping.mod_in(); + Ok(Self { + mapping, + delta_dim, + dim_in, + len_out, + len_in, + }) + } + } +} + +impl Mapping for Size { + fn len_in(&self) -> usize { + self.len_in + } + fn len_out(&self) -> usize { + self.len_out + } + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn add_offset(&mut self, offset: usize) { + self.mapping.add_offset(offset); + self.dim_in += offset; + } + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { + self.mapping + .apply_inplace(index, coordinates, self.dim_out()) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + self.mapping.apply_index(index) + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.mapping.apply_indices_inplace(indices) + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + self.mapping.unapply_indices(indices) + } + fn is_identity(&self) -> bool { + self.mapping.is_identity() + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ComposeCodomainDomainMismatch; + +impl std::error::Error for ComposeCodomainDomainMismatch {} + +impl std::fmt::Display for ComposeCodomainDomainMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "The codomain of the first maping doesn't match the domain of the second mapping," + ) + } +} + +#[derive(Debug, Clone)] +pub struct Composition(M1, M2); + +impl Composition { + pub fn new(mapping1: M1, mapping2: M2) -> Result { + if mapping1.len_out() == mapping2.len_in() && mapping1.dim_out() == mapping2.dim_in() { + Ok(Self(mapping1, mapping2)) + } else { + Err(ComposeCodomainDomainMismatch) + } + } +} + +impl Mapping for Composition { + fn len_in(&self) -> usize { + self.0.len_in() + } + fn len_out(&self) -> usize { + self.1.len_out() + } + fn dim_in(&self) -> usize { + self.0.dim_in() + } + fn delta_dim(&self) -> usize { + self.0.delta_dim() + self.1.delta_dim() + } + fn add_offset(&mut self, offset: usize) { + self.0.add_offset(offset); + self.1.add_offset(offset); + } + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { + let index = self.0.apply_inplace_unchecked(index, coordinates); + self.1.apply_inplace_unchecked(index, coordinates) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + let index = self.0.apply_index_unchecked(index); + self.1.apply_index_unchecked(index) + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.0.apply_indices_inplace_unchecked(indices); + self.1.apply_indices_inplace_unchecked(indices); + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + let indices = self.1.unapply_indices_unchecked(indices); + self.0.unapply_indices_unchecked(&indices) + } + fn is_identity(&self) -> bool { + self.0.is_identity() && self.1.is_identity() + } +} + +trait Compose: Mapping + Sized { + fn compose(self, rhs: Rhs) -> Result, ComposeCodomainDomainMismatch> { + Composition::new(self, rhs) + } +} + +impl Compose for M {} #[derive(Debug, Clone)] pub struct Chain { @@ -10,11 +164,11 @@ pub struct Chain { dim_in: usize, len_out: usize, len_in: usize, - chain: UnsizedChain, + chain: Vec, } impl Chain { - pub fn new(dim_out: usize, len_out: usize, chain: UnsizedChain) -> Self { + pub fn new(dim_out: usize, len_out: usize, chain: Vec) -> Self { assert!(chain.dim_out() <= dim_out); assert_eq!(len_out % chain.mod_out(), 0); let len_in = len_out / chain.mod_out() * chain.mod_in(); @@ -33,18 +187,15 @@ impl Chain { dim_in: dim, len_out: len, len_in: len, - chain: UnsizedChain::identity(), + chain: Vec::new(), } } - pub fn iter(&self) -> impl Iterator + DoubleEndedIterator { - self.chain.iter() - } - pub fn push(&mut self, operator: Operator) { - assert!(operator.dim_out() <= self.dim_in); - assert_eq!(self.len_in % operator.mod_out(), 0); - self.dim_in -= operator.delta_dim(); - self.len_in = self.len_in / operator.mod_out() * operator.mod_in(); - self.chain.push(operator); + pub fn push(&mut self, item: Elementary) { + assert!(item.dim_out() <= self.dim_in); + assert_eq!(self.len_in % item.mod_out(), 0); + self.dim_in -= item.delta_dim(); + self.len_in = self.len_in / item.mod_out() * item.mod_in(); + self.chain.push(item); } pub fn partial_relative_to(&self, target: &Self) -> Option<(Self, Option>)> { let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); @@ -60,15 +211,6 @@ impl Chain { Some((rel, Some(rel_indices))) } } - pub fn join(&self, other: &Self) -> Option { - // TODO: return Result - if self.dim_in() == other.dim_out() && self.len_in() == other.len_out() { - let ops = self.iter().chain(other.iter()).cloned().collect(); - Some(Chain::new(self.dim_out, self.len_out, ops)) - } else { - None - } - } } impl Mapping for Chain { @@ -109,23 +251,23 @@ impl Mapping for Chain { } } -impl Mul for Chain { - type Output = Self; - - fn mul(self, mut other: Self) -> Self { - let dim_out = self.dim_out() + other.dim_out(); - let len_out = self.len_out() * other.len_out(); - let trans1 = Operator::new_transpose(other.len_out(), self.len_out()); - let trans2 = Operator::new_transpose(self.len_in(), other.len_out()); - other.add_offset(self.dim_in()); - let chain: UnsizedChain = iter::once(trans1) - .chain(self.chain) - .chain(iter::once(trans2)) - .chain(other.chain) - .collect(); - Chain::new(dim_out, len_out, chain) - } -} +//impl Mul for Chain { +// type Output = Self; +// +// fn mul(self, mut other: Self) -> Self { +// let dim_out = self.dim_out() + other.dim_out(); +// let len_out = self.len_out() * other.len_out(); +// let trans1 = Elementary::new_transpose(other.len_out(), self.len_out()); +// let trans2 = Elementary::new_transpose(self.len_in(), other.len_out()); +// other.add_offset(self.dim_in()); +// let chain: Vec = iter::once(trans1) +// .chain(self.chain) +// .chain(iter::once(trans2)) +// .chain(other.chain) +// .collect(); +// Chain::new(dim_out, len_out, chain) +// } +//} #[derive(Debug, Clone)] struct Concat { @@ -234,7 +376,7 @@ trait RelativeTo { fn relative_to(&self, target: &Target) -> Option; } -impl RelativeTo for Chain { +impl RelativeTo for Chain { type Output = Self; fn relative_to(&self, target: &Self) -> Option { @@ -244,6 +386,19 @@ impl RelativeTo for Chain { } } +type ElemComp = Size>; + +impl RelativeTo for ElemComp { + type Output = Self; + + fn relative_to(&self, target: &Self) -> Option { + let (common, rem, rel) = target.mapping.remove_common_prefix_opt_lhs(&self.mapping); + rem.is_identity() + .then(|| Self::new(rel, target.dim_in(), target.len_in()).unwrap()) + // TODO: Self::new_unchecked + } +} + impl RelativeTo for Concat where Item: Mapping + RelativeTo, @@ -260,92 +415,44 @@ where } } -impl RelativeTo> for Chain { - type Output = RelativeToConcatChain; - - fn relative_to(&self, targets: &Concat) -> Option { - let mut rels_indices = Vec::new(); - let mut offset = 0; - for (itarget, target) in targets.items.iter().enumerate() { - let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); - // if rem.is_identity() { - // return rel + offset; - // } - if rem.dim_out() == 0 { - let mut rem_indices: Vec = (0..).take(target.len_in()).collect(); - rem.apply_indices_inplace(&mut rem_indices); - // TODO: First collect rem_indices for all targets, then remove - // the common tail from all rels and then unapply the rem - // indices on the rels. - rels_indices.push((rel, itarget, rem_indices, offset)) - } - offset += target.len_in(); - } - // TODO: Split off common tail. - // TODO: unapply indices and build map - // let rel_indices = rel.unapply_indices_unchecked(&rem_indices); - // if !rel_indices.is_empty() { - // // update map - // //tails.push((rel, offset, rel_indices)); - // } - // TODO: return None if we didn't find everything - unimplemented!{} - // RelativeToConcatChain { ... } - // Pair>>, Chain> - } -} - -#[derive(Debug, Clone)] -struct RelativeToConcatChain { - dim_in: usize, - delta_dim: usize, - len_out: usize, - map: Vec<(usize, usize)>, - parts: Vec<(UnsizedChain, usize)>, - // common_tail: UnsizedChain, -} - -impl Mapping for RelativeToConcatChain { - fn dim_in(&self) -> usize { - self.dim_in - } - fn delta_dim(&self) -> usize { - self.delta_dim - } - fn len_out(&self) -> usize { - self.len_out - } - fn len_in(&self) -> usize { - self.map.len() - } - fn add_offset(&mut self, offset: usize) { - for (chain, _) in self.parts.iter_mut() { - chain.add_offset(offset); - } - self.dim_in += offset; - } - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - let (ipart, index) = self.map[index]; - let (chain, offset) = &self.parts[ipart]; - offset + chain.apply_inplace(index, coordinates, self.dim_out()) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - let (ipart, index) = self.map[index]; - let (chain, offset) = &self.parts[ipart]; - offset + chain.apply_index(index) - } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { - unimplemented! {} - } - fn is_identity(&self) -> bool { - false - } -} +//impl RelativeTo> for Chain { +// type Output = RelativeToConcatChain; +// +// fn relative_to(&self, targets: &Concat) -> Option { +// let mut rels_indices = Vec::new(); +// let mut offset = 0; +// for (itarget, target) in targets.items.iter().enumerate() { +// let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); +// // if rem.is_identity() { +// // return rel + offset; +// // } +// if rem.dim_out() == 0 { +// let mut rem_indices: Vec = (0..).take(target.len_in()).collect(); +// rem.apply_indices_inplace(&mut rem_indices); +// // TODO: First collect rem_indices for all targets, then remove +// // the common tail from all rels and then unapply the rem +// // indices on the rels. +// rels_indices.push((rel, itarget, rem_indices, offset)) +// } +// offset += target.len_in(); +// } +// // TODO: Split off common tail. +// // TODO: unapply indices and build map +// // let rel_indices = rel.unapply_indices_unchecked(&rem_indices); +// // if !rel_indices.is_empty() { +// // // update map +// // //tails.push((rel, offset, rel_indices)); +// // } +// // TODO: return None if we didn't find everything +// unimplemented! {} +// // RelativeToConcatChain { ... } +// // Pair>>, Chain> +// } +//} #[cfg(test)] mod tests { use super::*; - use crate::operator::Operator; use crate::simplex::Simplex::*; use approx::assert_abs_diff_eq; @@ -353,8 +460,8 @@ mod tests { (dim=$dim_out:literal, len=$len_out:literal) => { Chain::identity($dim_out, $len_out) }; - (dim=$dim_out:literal, len=$len_out:literal <- $($op:expr),*) => { - Chain::new($dim_out, $len_out, vec![$(Operator::from($op)),*].into()) + (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { + Chain::new($dim_out, $len_out, vec![$(Elementary::from($item)),*]) }; } @@ -405,21 +512,21 @@ mod tests { let mut rel_indices: Vec<_> = (0..rel.len_in()).collect(); rel_indices.sort_by_key(|&i| &tmp[i]); let mut b = b.clone(); - b.push(Operator::new_take(b_indices, b.len_in())); + b.push(Elementary::new_take(b_indices, b.len_in())); let mut rel = rel.clone(); - rel.push(Operator::new_take(rel_indices, rel.len_in())); + rel.push(Elementary::new_take(rel_indices, rel.len_in())); (rel, b) } else { (rel, b) }; - assert_equiv_chains!(a.join(&rel_compressed).unwrap(), &b_compressed $(, $simplex)*); + assert_equiv_chains!(rel_compressed.compose(a).unwrap(), &b_compressed $(, $simplex)*); }}; } #[test] fn partial_relative_to() { - use crate::operator::*; + use crate::elementary::*; assert_partial_relative_to!( chain!(dim=1, len=2 <- Children::new(Line), Take::new([0,3,1], 4)), chain!(dim=1, len=2 <- Children::new(Line), Children::new(Line)), @@ -434,4 +541,11 @@ mod tests { chain!(dim=1, len=2 <- Edges::new(Line)) ); } + + #[test] + fn rel_to() { + let a = Size::new(vec![Elementary::new_children(Line)], 1, 2).unwrap(); + let b = Size::new(vec![Elementary::new_children(Line), Elementary::new_children(Line)], 1, 2).unwrap(); + assert_eq!(b.relative_to(&a), Some(Size::new(vec![Elementary::new_children(Line)], 1, 4).unwrap())); + } } From 90287450155cb39c89677840f4f7a8f242bedbbd Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Mon, 20 Jun 2022 13:10:26 +0200 Subject: [PATCH 13/45] WIP --- src/chain.rs | 190 ---------- src/finite.rs | 289 +++++++++++++++ src/{elementary.rs => infinite.rs} | 127 ++++++- src/lib.rs | 88 +---- src/relative_to.rs | 285 +++++++++++++++ src/sequence.rs | 551 ----------------------------- 6 files changed, 697 insertions(+), 833 deletions(-) delete mode 100644 src/chain.rs create mode 100644 src/finite.rs rename src/{elementary.rs => infinite.rs} (86%) create mode 100644 src/relative_to.rs delete mode 100644 src/sequence.rs diff --git a/src/chain.rs b/src/chain.rs deleted file mode 100644 index 4e6b11906..000000000 --- a/src/chain.rs +++ /dev/null @@ -1,190 +0,0 @@ -use crate::elementary::{Edges, Elementary, Transpose}; -use crate::simplex::Simplex; -use crate::UnsizedMapping; -use num::Integer as _; -use std::collections::BTreeMap; -use std::ops::{Deref, DerefMut}; - -fn dim_out_in(items: &[M]) -> (usize, usize) { - let mut dim_in = 0; - let mut dim_out = 0; - for item in items.iter().rev() { - if let Some(n) = item.dim_in().checked_sub(dim_out) { - dim_in += n; - dim_out += n; - } - dim_out += item.delta_dim(); - } - (dim_out, dim_in) -} - -fn mod_out_in(items: &[M]) -> (usize, usize) { - let mut mod_out = 1; - let mut mod_in = 1; - for item in items.iter().rev() { - let n = mod_out.lcm(&item.mod_in()); - mod_in *= n / mod_out; - mod_out = n / item.mod_in() * item.mod_out(); - } - (mod_out, mod_in) -} - -impl UnsizedMapping for T -where - M: UnsizedMapping, - T: Deref + DerefMut, -{ - fn dim_in(&self) -> usize { - dim_out_in(self.deref()).1 - } - fn delta_dim(&self) -> usize { - self.iter().map(|item| item.delta_dim()).sum() - } - fn add_offset(&mut self, offset: usize) { - for item in self.iter_mut() { - item.add_offset(offset); - } - } - fn mod_in(&self) -> usize { - mod_out_in(self.deref()).1 - } - fn mod_out(&self) -> usize { - mod_out_in(self.deref()).0 - } - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { - self.iter().rev().fold(index, |index, item| { - item.apply_inplace(index, coordinates, stride) - }) - } - fn apply_index(&self, index: usize) -> usize { - self.iter() - .rev() - .fold(index, |index, item| item.apply_index(index)) - } - fn apply_indices_inplace(&self, indices: &mut [usize]) { - for item in self.iter().rev() { - item.apply_indices_inplace(indices); - } - } - fn unapply_indices(&self, indices: &[usize]) -> Vec { - self.iter().fold(indices.to_vec(), |indices, item| { - item.unapply_indices(&indices) - }) - } -} - -pub trait RemoveCommonPrefix: Sized { - fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self); - fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self); -} - -fn split_heads( - items: &[Elementary], -) -> BTreeMap, Vec)> { - let mut heads = BTreeMap::new(); - for (i, item) in items.iter().enumerate() { - if let Some((transpose, head, mut tail)) = item.shift_left(&items[..i]) { - tail.extend(items[i + 1..].iter().cloned()); - heads.insert(head, (transpose, tail)); - } - if let Elementary::Edges(Edges(Simplex::Line, offset)) = item { - let children = Elementary::new_children(Simplex::Line).with_offset(*offset); - if let Some((transpose, head, mut tail)) = children.shift_left(&items[..i]) { - tail.push(item.clone()); - tail.push(Elementary::new_take( - Simplex::Line.swap_edges_children_map(), - Simplex::Line.nedges() * Simplex::Line.nchildren(), - )); - tail.extend(items[i + 1..].iter().cloned()); - heads.insert(head, (transpose, tail)); - } - } - } - heads -} - -impl RemoveCommonPrefix for Vec { - /// Remove and return the common prefix of two chains, transforming either if necessary. - fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { - let mut common = Vec::new(); - let mut tail1 = self.clone(); - let mut tail2 = other.clone(); - while !tail1.is_empty() && !tail2.is_empty() { - if tail1.last() == tail2.last() { - common.push(tail1.pop().unwrap()); - tail2.pop(); - continue; - } - let heads1: BTreeMap> = split_heads(&tail1) - .into_iter() - .filter_map(|(head, (trans, tail))| trans.is_none().then(|| (head, tail))) - .collect(); - let mut heads2: BTreeMap> = split_heads(&tail2) - .into_iter() - .filter_map(|(head, (trans, tail))| trans.is_none().then(|| (head, tail))) - .collect(); - if let Some((head, new_tail1, new_tail2)) = heads1 - .into_iter() - .filter_map(|(h, t1)| heads2.remove(&h).map(|t2| (h, t1, t2))) - .min_by_key(|(_, t1, t2)| std::cmp::max(t1.len(), t2.len())) - { - common.push(head); - tail1 = new_tail1; - tail2 = new_tail2; - continue; - } - break; - } - let common = if tail1.is_empty() && (!tail2.is_empty() || self.len() <= other.len()) { - self.clone() - } else if tail2.is_empty() { - other.clone() - } else { - common - }; - (common, tail1, tail2) - } - fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self) { - let (common, tail1, tail2) = self.remove_common_prefix(other); - // Move transposes at the front of `tail1` to `tail2`. - let mut tail1: Vec<_> = tail1.into(); - let mut tail2: Vec<_> = tail2.into(); - tail1.reverse(); - tail2.reverse(); - while let Some(mut item) = tail1.pop() { - if let Some(transpose) = item.as_transpose_mut() { - transpose.reverse(); - tail2.push(item); - } else { - tail1.push(item); - break; - } - } - tail1.reverse(); - tail2.reverse(); - (common, tail1.into(), tail2.into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::simplex::Simplex::*; - - #[test] - fn remove_common_prefix() { - let c1 = Elementary::new_children(Line); - let e1 = Elementary::new_edges(Line); - let swap_ec1 = Elementary::new_take([2, 1], 4); - let a = vec![c1.clone(), c1.clone()]; - let b = vec![e1.clone()]; - assert_eq!( - a.remove_common_prefix(&b), - ( - vec![c1.clone(), c1.clone()], - vec![], - vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], - ) - ); - } -} diff --git a/src/finite.rs b/src/finite.rs new file mode 100644 index 000000000..2a7bc0eee --- /dev/null +++ b/src/finite.rs @@ -0,0 +1,289 @@ +use crate::infinite::InfiniteMapping; + +pub trait Mapping { + fn len_out(&self) -> usize; + fn len_in(&self) -> usize; + fn dim_out(&self) -> usize { + self.dim_in() + self.delta_dim() + } + fn dim_in(&self) -> usize; + fn delta_dim(&self) -> usize; + fn add_offset(&mut self, offset: usize); + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize; + fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { + if index < self.len_in() { + Some(self.apply_inplace_unchecked(index, coordinates)) + } else { + None + } + } + fn apply_index_unchecked(&self, index: usize) -> usize; + fn apply_index(&self, index: usize) -> Option { + if index < self.len_in() { + Some(self.apply_index_unchecked(index)) + } else { + None + } + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + for index in indices.iter_mut() { + *index = self.apply_index_unchecked(*index); + } + } + fn apply_indices(&self, indices: &[usize]) -> Option> { + if indices.iter().all(|index| *index < self.len_in()) { + let mut indices = indices.to_vec(); + self.apply_indices_inplace_unchecked(&mut indices); + Some(indices) + } else { + None + } + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec; + fn unapply_indices(&self, indices: &[usize]) -> Option> { + if indices.iter().all(|index| *index < self.len_out()) { + Some(self.unapply_indices_unchecked(indices)) + } else { + None + } + } + fn is_identity(&self) -> bool; +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ComposeCodomainDomainMismatch; + +impl std::error::Error for ComposeCodomainDomainMismatch {} + +impl std::fmt::Display for ComposeCodomainDomainMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "The codomain of the first maping doesn't match the domain of the second mapping," + ) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Composition(M1, M2); + +impl Composition { + pub fn new(mapping1: M1, mapping2: M2) -> Result { + if mapping1.len_out() == mapping2.len_in() && mapping1.dim_out() == mapping2.dim_in() { + Ok(Self(mapping1, mapping2)) + } else { + Err(ComposeCodomainDomainMismatch) + } + } +} + +impl Mapping for Composition { + fn len_in(&self) -> usize { + self.0.len_in() + } + fn len_out(&self) -> usize { + self.1.len_out() + } + fn dim_in(&self) -> usize { + self.0.dim_in() + } + fn delta_dim(&self) -> usize { + self.0.delta_dim() + self.1.delta_dim() + } + fn add_offset(&mut self, offset: usize) { + self.0.add_offset(offset); + self.1.add_offset(offset); + } + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { + let index = self.0.apply_inplace_unchecked(index, coordinates); + self.1.apply_inplace_unchecked(index, coordinates) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + let index = self.0.apply_index_unchecked(index); + self.1.apply_index_unchecked(index) + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.0.apply_indices_inplace_unchecked(indices); + self.1.apply_indices_inplace_unchecked(indices); + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + let indices = self.1.unapply_indices_unchecked(indices); + self.0.unapply_indices_unchecked(&indices) + } + fn is_identity(&self) -> bool { + self.0.is_identity() && self.1.is_identity() + } +} + +pub trait Compose: Mapping + Sized { + fn compose(self, rhs: Rhs) -> Result, ComposeCodomainDomainMismatch> { + Composition::new(self, rhs) + } +} + +impl Compose for M {} + +#[derive(Debug, Clone, PartialEq)] +pub struct Concatenation { + dim_in: usize, + delta_dim: usize, + len_out: usize, + len_in: usize, + items: Vec, +} + +impl Concatenation { + pub fn new(items: Vec) -> Self { + // TODO: Return `Result`. + let first = items.first().unwrap(); + let dim_in = first.dim_in(); + let delta_dim = first.delta_dim(); + let len_out = first.len_out(); + let mut len_in = 0; + for item in items.iter() { + assert_eq!(item.dim_in(), dim_in); + assert_eq!(item.delta_dim(), delta_dim); + assert_eq!(item.len_out(), len_out); + len_in += item.len_in(); + } + Self { + dim_in, + delta_dim, + len_out, + len_in, + items, + } + } + fn resolve_item_unchecked(&self, mut index: usize) -> (&Item, usize) { + for item in self.items.iter() { + if index < item.len_in() { + return (item, index); + } + index -= item.len_in(); + } + panic!("index out of range"); + } + pub fn iter(&self) -> impl Iterator { + self.items.iter() + } +} + +impl Mapping for Concatenation { + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn len_out(&self) -> usize { + self.len_out + } + fn len_in(&self) -> usize { + self.len_in + } + fn add_offset(&mut self, offset: usize) { + for item in self.items.iter_mut() { + item.add_offset(offset); + } + self.dim_in += offset; + } + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { + let (item, index) = self.resolve_item_unchecked(index); + item.apply_inplace_unchecked(index, coordinates) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + let (item, index) = self.resolve_item_unchecked(index); + item.apply_index_unchecked(index) + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + unimplemented! {} + } + fn is_identity(&self) -> bool { + false + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum NewBoundedError { + DimensionTooSmall, + LengthNotAMultipleOfRepetition, +} + +impl std::error::Error for NewBoundedError {} + +impl std::fmt::Display for NewBoundedError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::DimensionTooSmall => write!(f, "The dimension of the sized mapping is smaller than the minimum dimension of the unsized mapping."), + Self::LengthNotAMultipleOfRepetition => write!(f, "The length of the sized mapping is not a multiple of the repetition length of the unsized mapping."), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Bounded { + mapping: M, + delta_dim: usize, + dim_in: usize, + len_out: usize, + len_in: usize, +} + +impl Bounded { + pub fn new(mapping: M, dim_out: usize, len_out: usize) -> Result { + if dim_out < mapping.dim_out() { + Err(NewBoundedError::DimensionTooSmall) + } else if len_out % mapping.mod_out() != 0 { + Err(NewBoundedError::LengthNotAMultipleOfRepetition) + } else { + let delta_dim = mapping.delta_dim(); + let dim_in = dim_out - delta_dim; + let len_in = len_out / mapping.mod_out() * mapping.mod_in(); + Ok(Self { + mapping, + delta_dim, + dim_in, + len_out, + len_in, + }) + } + } + pub fn get_unsized(&self) -> &M { + &self.mapping + } +} + +impl Mapping for Bounded { + fn len_in(&self) -> usize { + self.len_in + } + fn len_out(&self) -> usize { + self.len_out + } + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn add_offset(&mut self, offset: usize) { + self.mapping.add_offset(offset); + self.dim_in += offset; + } + fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { + self.mapping + .apply_inplace(index, coordinates, self.dim_out()) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + self.mapping.apply_index(index) + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.mapping.apply_indices_inplace(indices) + } + fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + self.mapping.unapply_indices(indices) + } + fn is_identity(&self) -> bool { + self.mapping.is_identity() + } +} diff --git a/src/elementary.rs b/src/infinite.rs similarity index 86% rename from src/elementary.rs rename to src/infinite.rs index d04c69e8d..c123790f6 100644 --- a/src/elementary.rs +++ b/src/infinite.rs @@ -1,8 +1,38 @@ use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; -use crate::UnsizedMapping; -use num::Integer; +use num::Integer as _; use std::rc::Rc; +use std::ops::{Deref, DerefMut}; + +pub trait InfiniteMapping { + // Minimum dimension of the input coordinate. If the dimension of the input + // coordinate of [InfiniteMapping::apply()] is larger than the minimum, then + // the mapping of the surplus is the identity mapping. + fn dim_in(&self) -> usize; + // Minimum dimension of the output coordinate. + fn dim_out(&self) -> usize { + self.dim_in() + self.delta_dim() + } + // Difference in dimension of the output and input coordinate. + fn delta_dim(&self) -> usize; + fn add_offset(&mut self, offset: usize); + // Modulus of the input index. The mapping repeats itself at index `mod_in` + // and the output index is incremented with `in_index / mod_in * mod_out`. + fn mod_in(&self) -> usize; + // Modulus if the output index. + fn mod_out(&self) -> usize; + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize; + fn apply_index(&self, index: usize) -> usize; + fn apply_indices_inplace(&self, indices: &mut [usize]) { + for index in indices.iter_mut() { + *index = self.apply_index(*index); + } + } + fn unapply_indices(&self, indices: &[usize]) -> Vec; + fn is_identity(&self) -> bool { + self.mod_in() == 1 && self.mod_out() == 1 && self.dim_out() == 0 + } +} #[inline] const fn divmod(x: usize, y: usize) -> (usize, usize) { @@ -40,7 +70,7 @@ impl Transpose { } } -impl UnsizedMapping for Transpose { +impl InfiniteMapping for Transpose { fn dim_in(&self) -> usize { 0 } @@ -97,7 +127,7 @@ impl Take { } } -impl UnsizedMapping for Take { +impl InfiniteMapping for Take { fn dim_in(&self) -> usize { 0 } @@ -141,7 +171,7 @@ impl Children { } } -impl UnsizedMapping for Children { +impl InfiniteMapping for Children { fn dim_in(&self) -> usize { self.0.dim() + self.1 } @@ -172,6 +202,12 @@ impl UnsizedMapping for Children { } } +impl From for Children { + fn from(simplex: Simplex) -> Children { + Children::new(simplex) + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Edges(pub Simplex, pub usize); @@ -181,7 +217,7 @@ impl Edges { } } -impl UnsizedMapping for Edges { +impl InfiniteMapping for Edges { fn dim_in(&self) -> usize { self.0.edge_dim() + self.1 } @@ -212,6 +248,12 @@ impl UnsizedMapping for Edges { } } +impl From for Edges { + fn from(simplex: Simplex) -> Edges { + Edges::new(simplex) + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct UniformPoints { points: Rc>, @@ -235,7 +277,7 @@ impl UniformPoints { } } -impl UnsizedMapping for UniformPoints { +impl InfiniteMapping for UniformPoints { fn dim_in(&self) -> usize { self.offset } @@ -454,7 +496,7 @@ macro_rules! dispatch { }; } -impl UnsizedMapping for Elementary { +impl InfiniteMapping for Elementary { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn add_offset(&mut self, offset: usize)} @@ -497,6 +539,75 @@ impl From for Elementary { } } +fn dim_out_in(items: &[M]) -> (usize, usize) { + let mut dim_in = 0; + let mut dim_out = 0; + for item in items.iter().rev() { + if let Some(n) = item.dim_in().checked_sub(dim_out) { + dim_in += n; + dim_out += n; + } + dim_out += item.delta_dim(); + } + (dim_out, dim_in) +} + +fn mod_out_in(items: &[M]) -> (usize, usize) { + let mut mod_out = 1; + let mut mod_in = 1; + for item in items.iter().rev() { + let n = mod_out.lcm(&item.mod_in()); + mod_in *= n / mod_out; + mod_out = n / item.mod_in() * item.mod_out(); + } + (mod_out, mod_in) +} + +/// Composition. +impl InfiniteMapping for Array +where + Item: InfiniteMapping, + Array: Deref + DerefMut, +{ + fn dim_in(&self) -> usize { + dim_out_in(self.deref()).1 + } + fn delta_dim(&self) -> usize { + self.iter().map(|item| item.delta_dim()).sum() + } + fn add_offset(&mut self, offset: usize) { + for item in self.iter_mut() { + item.add_offset(offset); + } + } + fn mod_in(&self) -> usize { + mod_out_in(self.deref()).1 + } + fn mod_out(&self) -> usize { + mod_out_in(self.deref()).0 + } + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { + self.iter().rev().fold(index, |index, item| { + item.apply_inplace(index, coordinates, stride) + }) + } + fn apply_index(&self, index: usize) -> usize { + self.iter() + .rev() + .fold(index, |index, item| item.apply_index(index)) + } + fn apply_indices_inplace(&self, indices: &mut [usize]) { + for item in self.iter().rev() { + item.apply_indices_inplace(indices); + } + } + fn unapply_indices(&self, indices: &[usize]) -> Vec { + self.iter().fold(indices.to_vec(), |indices, item| { + item.unapply_indices(&indices) + }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index fffb876e9..a60404fce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,85 +1,5 @@ -pub mod chain; -pub mod elementary; -mod finite_f64; -pub mod sequence; +pub mod finite_f64; pub mod simplex; - -pub trait Mapping { - fn len_out(&self) -> usize; - fn len_in(&self) -> usize; - fn dim_out(&self) -> usize { - self.dim_in() + self.delta_dim() - } - fn dim_in(&self) -> usize; - fn delta_dim(&self) -> usize; - fn add_offset(&mut self, offset: usize); - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize; - fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { - if index < self.len_in() { - Some(self.apply_inplace_unchecked(index, coordinates)) - } else { - None - } - } - fn apply_index_unchecked(&self, index: usize) -> usize; - fn apply_index(&self, index: usize) -> Option { - if index < self.len_in() { - Some(self.apply_index_unchecked(index)) - } else { - None - } - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - for index in indices.iter_mut() { - *index = self.apply_index_unchecked(*index); - } - } - fn apply_indices(&self, indices: &[usize]) -> Option> { - if indices.iter().all(|index| *index < self.len_in()) { - let mut indices = indices.to_vec(); - self.apply_indices_inplace_unchecked(&mut indices); - Some(indices) - } else { - None - } - } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec; - fn unapply_indices(&self, indices: &[usize]) -> Option> { - if indices.iter().all(|index| *index < self.len_out()) { - Some(self.unapply_indices_unchecked(indices)) - } else { - None - } - } - fn is_identity(&self) -> bool; -} - -pub trait UnsizedMapping { - // Minimum dimension of the input coordinate. If the dimension of the input - // coordinate of [UnsizedMapping::apply()] is larger than the minimum, then - // the mapping of the surplus is the identity mapping. - fn dim_in(&self) -> usize; - // Minimum dimension of the output coordinate. - fn dim_out(&self) -> usize { - self.dim_in() + self.delta_dim() - } - // Difference in dimension of the output and input coordinate. - fn delta_dim(&self) -> usize; - fn add_offset(&mut self, offset: usize); - // Modulus of the input index. The mapping repeats itself at index `mod_in` - // and the output index is incremented with `in_index / mod_in * mod_out`. - fn mod_in(&self) -> usize; - // Modulus if the output index. - fn mod_out(&self) -> usize; - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize; - fn apply_index(&self, index: usize) -> usize; - fn apply_indices_inplace(&self, indices: &mut [usize]) { - for index in indices.iter_mut() { - *index = self.apply_index(*index); - } - } - fn unapply_indices(&self, indices: &[usize]) -> Vec; - fn is_identity(&self) -> bool { - self.mod_in() == 1 && self.mod_out() == 1 && self.dim_out() == 0 - } -} +pub mod infinite; +pub mod finite; +pub mod relative_to; diff --git a/src/relative_to.rs b/src/relative_to.rs new file mode 100644 index 000000000..754fbc984 --- /dev/null +++ b/src/relative_to.rs @@ -0,0 +1,285 @@ +use crate::finite::Mapping; +use crate::infinite::{InfiniteMapping, Edges, Elementary, Transpose}; +use crate::simplex::Simplex; +use std::collections::BTreeMap; +use crate::finite::{Bounded, Concatenation, Composition, Compose}; + +trait RemoveCommonPrefix: Sized { + fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self); + fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self); +} + +fn split_heads( + items: &[Elementary], +) -> BTreeMap, Vec)> { + let mut heads = BTreeMap::new(); + for (i, item) in items.iter().enumerate() { + if let Some((transpose, head, mut tail)) = item.shift_left(&items[..i]) { + tail.extend(items[i + 1..].iter().cloned()); + heads.insert(head, (transpose, tail)); + } + if let Elementary::Edges(Edges(Simplex::Line, offset)) = item { + let children = Elementary::new_children(Simplex::Line).with_offset(*offset); + if let Some((transpose, head, mut tail)) = children.shift_left(&items[..i]) { + tail.push(item.clone()); + tail.push(Elementary::new_take( + Simplex::Line.swap_edges_children_map(), + Simplex::Line.nedges() * Simplex::Line.nchildren(), + )); + tail.extend(items[i + 1..].iter().cloned()); + heads.insert(head, (transpose, tail)); + } + } + } + heads +} + +impl RemoveCommonPrefix for Vec { + /// Remove and return the common prefix of two chains, transforming either if necessary. + fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { + let mut common = Vec::new(); + let mut tail1 = self.clone(); + let mut tail2 = other.clone(); + while !tail1.is_empty() && !tail2.is_empty() { + if tail1.last() == tail2.last() { + common.push(tail1.pop().unwrap()); + tail2.pop(); + continue; + } + let heads1: BTreeMap> = split_heads(&tail1) + .into_iter() + .filter_map(|(head, (trans, tail))| trans.is_none().then(|| (head, tail))) + .collect(); + let mut heads2: BTreeMap> = split_heads(&tail2) + .into_iter() + .filter_map(|(head, (trans, tail))| trans.is_none().then(|| (head, tail))) + .collect(); + if let Some((head, new_tail1, new_tail2)) = heads1 + .into_iter() + .filter_map(|(h, t1)| heads2.remove(&h).map(|t2| (h, t1, t2))) + .min_by_key(|(_, t1, t2)| std::cmp::max(t1.len(), t2.len())) + { + common.push(head); + tail1 = new_tail1; + tail2 = new_tail2; + continue; + } + break; + } + let common = if tail1.is_empty() && (!tail2.is_empty() || self.len() <= other.len()) { + self.clone() + } else if tail2.is_empty() { + other.clone() + } else { + common + }; + (common, tail1, tail2) + } + fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self) { + let (common, tail1, tail2) = self.remove_common_prefix(other); + // Move transposes at the front of `tail1` to `tail2`. + let mut tail1: Vec<_> = tail1.into(); + let mut tail2: Vec<_> = tail2.into(); + tail1.reverse(); + tail2.reverse(); + while let Some(mut item) = tail1.pop() { + if let Some(transpose) = item.as_transpose_mut() { + transpose.reverse(); + tail2.push(item); + } else { + tail1.push(item); + break; + } + } + tail1.reverse(); + tail2.reverse(); + (common, tail1.into(), tail2.into()) + } +} + +trait RelativeTo { + type Output: Mapping; + + fn relative_to(&self, target: &Target) -> Option; +} + +type ElemComp = Bounded>; + +impl RelativeTo for ElemComp { + type Output = Self; + + fn relative_to(&self, target: &Self) -> Option { + let (common, rem, rel) = target.get_unsized().remove_common_prefix_opt_lhs(&self.get_unsized()); + rem.is_identity() + .then(|| Self::new(rel, target.dim_in(), target.len_in()).unwrap()) + // TODO: Self::new_unchecked + } +} + +impl RelativeTo for Concatenation +where + Item: Mapping + RelativeTo, + Target: Mapping, +{ + type Output = Concatenation; + + fn relative_to(&self, target: &Target) -> Option { + self.iter() + .map(|item| item.relative_to(target)) + .collect::>() + .map(|rel_items| Concatenation::new(rel_items)) + } +} + +//impl RelativeTo> for ElemComp { +// type Output = Composition>; +// +// fn relative_to(&self, targets: &Concatenation) -> Option { +// let mut rels_indices = Vec::new(); +// let mut offset = 0; +// for (itarget, target) in targets.items.iter().enumerate() { +// let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); +// // if rem.is_identity() { +// // return rel + offset; +// // } +// if rem.dim_out() == 0 { +// let mut rem_indices: Vec = (0..).take(target.len_in()).collect(); +// rem.apply_indices_inplace(&mut rem_indices); +// // TODO: First collect rem_indices for all targets, then remove +// // the common tail from all rels and then unapply the rem +// // indices on the rels. +// rels_indices.push((rel, itarget, rem_indices, offset)) +// } +// offset += target.len_in(); +// } +// // TODO: Split off common tail. +// // TODO: unapply indices and build map +// // let rel_indices = rel.unapply_indices_unchecked(&rem_indices); +// // if !rel_indices.is_empty() { +// // // update map +// // //tails.push((rel, offset, rel_indices)); +// // } +// // TODO: return None if we didn't find everything +// unimplemented! {} +// // RelativeToConcatenateChain { ... } +// // Composition> +// } +//} + +#[cfg(test)] +mod tests { + use super::*; + use crate::simplex::Simplex::*; + use approx::assert_abs_diff_eq; + use crate::infinite::*; + use std::iter; + + #[test] + fn remove_common_prefix() { + let c1 = Elementary::new_children(Line); + let e1 = Elementary::new_edges(Line); + let swap_ec1 = Elementary::new_take([2, 1], 4); + let a = vec![c1.clone(), c1.clone()]; + let b = vec![e1.clone()]; + assert_eq!( + a.remove_common_prefix(&b), + ( + vec![c1.clone(), c1.clone()], + vec![], + vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], + ) + ); + } + + macro_rules! elem_comp { + (dim=$dim_out:literal, len=$len_out:literal) => { + ElemComp::new(Vec::new(), $dim_out, $len_out).unwrap() + }; + (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { + ElemComp::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() + }; + } + + macro_rules! assert_equiv_chains { + ($a:expr, $b:expr $(, $simplex:ident)*) => {{ + let a = $a; + let b = $b; + println!("a: {a:?}"); + println!("b: {b:?}"); + // Build coords: the outer product of the vertices of the given simplices, zero-padded + // to the dimension of the root. + let coords = iter::once([]); + let simplex_dim = 0; + $( + let coords = coords.flat_map(|coord| { + $simplex + .vertices() + .chunks($simplex.dim()) + .map(move |vert| [&coord, vert].concat()) + }); + let simplex_dim = simplex_dim + $simplex.dim(); + )* + assert_eq!(simplex_dim, a.dim_in(), "given simplices don't add up to the input dimension"); + let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); + let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); + // Test if every input maps to the same output for both `a` and `b`. + for i in 0..2 * a.len_in() { + let mut crds_a = coords.clone(); + let mut crds_b = coords.clone(); + let ja = a.apply_inplace(i, &mut crds_a); + let jb = b.apply_inplace(i, &mut crds_b); + assert_eq!(ja, jb, "i={i}"); + assert_abs_diff_eq!(crds_a[..], crds_b[..]); + } + }}; + } + +// macro_rules! assert_partial_relative_to { +// ($a:expr, $b:expr $(, $simplex:ident)*) => {{ +// let a = $a; +// let b = $b; +// let (rel, indices) = b.partial_relative_to(&a).unwrap(); +// +// let (rel_compressed, b_compressed) = if let Some(indices) = indices { +// let b_indices: Vec<_> = (0..).take(b.len_in()).filter_map(|i| indices.contains(&i).then(|| i)).collect(); +// let mut tmp: Vec<_> = (0..rel.len_in()).collect(); +// tmp.sort_by_key(|&i| &indices[i]); +// let mut rel_indices: Vec<_> = (0..rel.len_in()).collect(); +// rel_indices.sort_by_key(|&i| &tmp[i]); +// let mut b = b.clone(); +// b.push(Elementary::new_take(b_indices, b.len_in())); +// let mut rel = rel.clone(); +// rel.push(Elementary::new_take(rel_indices, rel.len_in())); +// (rel, b) +// } else { +// (rel, b) +// }; +// +// assert_equiv_chains!(rel_compressed.compose(a).unwrap(), &b_compressed $(, $simplex)*); +// }}; +// } +// +// #[test] +// fn partial_relative_to() { +// assert_partial_relative_to!( +// elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([0,3,1], 4)), +// elem_comp!(dim=1, len=2 <- Children::new(Line), Children::new(Line)), +// Line +// ); +// assert_partial_relative_to!( +// elem_comp!(dim=0, len=4 <- Take::new([0,3,1], 4)), +// elem_comp!(dim = 0, len = 4) +// ); +// assert_partial_relative_to!( +// elem_comp!(dim=1, len=2 <- Children::new(Line)), +// elem_comp!(dim=1, len=2 <- Edges::new(Line)) +// ); +// } + + #[test] + fn rel_to() { + let a = elem_comp!(dim=1, len=2 <- Children::new(Line)); + let b = elem_comp!(dim=1, len=2 <- Children::new(Line), Children::new(Line)); + assert_eq!(b.relative_to(&a), Some(elem_comp!(dim=1, len=4 <- Children::new(Line)))); + } +} diff --git a/src/sequence.rs b/src/sequence.rs deleted file mode 100644 index 39c0ac580..000000000 --- a/src/sequence.rs +++ /dev/null @@ -1,551 +0,0 @@ -use crate::chain::RemoveCommonPrefix; -use crate::elementary::Elementary; -use crate::{Mapping, UnsizedMapping}; -use std::iter; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum NewSizeError { - DimensionTooSmall, - LengthNotAMultipleOfRepetition, -} - -impl std::error::Error for NewSizeError {} - -impl std::fmt::Display for NewSizeError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::DimensionTooSmall => write!(f, "The dimension of the sized mapping is smaller than the minimum dimension of the unsized mapping."), - Self::LengthNotAMultipleOfRepetition => write!(f, "The length of the sized mapping is not a multiple of the repetition length of the unsized mapping."), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Size { - mapping: M, - delta_dim: usize, - dim_in: usize, - len_out: usize, - len_in: usize, -} - -impl Size { - pub fn new(mapping: M, dim_out: usize, len_out: usize) -> Result { - if dim_out < mapping.dim_out() { - Err(NewSizeError::DimensionTooSmall) - } else if len_out % mapping.mod_out() != 0 { - Err(NewSizeError::LengthNotAMultipleOfRepetition) - } else { - let delta_dim = mapping.delta_dim(); - let dim_in = dim_out - delta_dim; - let len_in = len_out / mapping.mod_out() * mapping.mod_in(); - Ok(Self { - mapping, - delta_dim, - dim_in, - len_out, - len_in, - }) - } - } -} - -impl Mapping for Size { - fn len_in(&self) -> usize { - self.len_in - } - fn len_out(&self) -> usize { - self.len_out - } - fn dim_in(&self) -> usize { - self.dim_in - } - fn delta_dim(&self) -> usize { - self.delta_dim - } - fn add_offset(&mut self, offset: usize) { - self.mapping.add_offset(offset); - self.dim_in += offset; - } - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - self.mapping - .apply_inplace(index, coordinates, self.dim_out()) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - self.mapping.apply_index(index) - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.mapping.apply_indices_inplace(indices) - } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { - self.mapping.unapply_indices(indices) - } - fn is_identity(&self) -> bool { - self.mapping.is_identity() - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct ComposeCodomainDomainMismatch; - -impl std::error::Error for ComposeCodomainDomainMismatch {} - -impl std::fmt::Display for ComposeCodomainDomainMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "The codomain of the first maping doesn't match the domain of the second mapping," - ) - } -} - -#[derive(Debug, Clone)] -pub struct Composition(M1, M2); - -impl Composition { - pub fn new(mapping1: M1, mapping2: M2) -> Result { - if mapping1.len_out() == mapping2.len_in() && mapping1.dim_out() == mapping2.dim_in() { - Ok(Self(mapping1, mapping2)) - } else { - Err(ComposeCodomainDomainMismatch) - } - } -} - -impl Mapping for Composition { - fn len_in(&self) -> usize { - self.0.len_in() - } - fn len_out(&self) -> usize { - self.1.len_out() - } - fn dim_in(&self) -> usize { - self.0.dim_in() - } - fn delta_dim(&self) -> usize { - self.0.delta_dim() + self.1.delta_dim() - } - fn add_offset(&mut self, offset: usize) { - self.0.add_offset(offset); - self.1.add_offset(offset); - } - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - let index = self.0.apply_inplace_unchecked(index, coordinates); - self.1.apply_inplace_unchecked(index, coordinates) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - let index = self.0.apply_index_unchecked(index); - self.1.apply_index_unchecked(index) - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.0.apply_indices_inplace_unchecked(indices); - self.1.apply_indices_inplace_unchecked(indices); - } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { - let indices = self.1.unapply_indices_unchecked(indices); - self.0.unapply_indices_unchecked(&indices) - } - fn is_identity(&self) -> bool { - self.0.is_identity() && self.1.is_identity() - } -} - -trait Compose: Mapping + Sized { - fn compose(self, rhs: Rhs) -> Result, ComposeCodomainDomainMismatch> { - Composition::new(self, rhs) - } -} - -impl Compose for M {} - -#[derive(Debug, Clone)] -pub struct Chain { - dim_out: usize, - dim_in: usize, - len_out: usize, - len_in: usize, - chain: Vec, -} - -impl Chain { - pub fn new(dim_out: usize, len_out: usize, chain: Vec) -> Self { - assert!(chain.dim_out() <= dim_out); - assert_eq!(len_out % chain.mod_out(), 0); - let len_in = len_out / chain.mod_out() * chain.mod_in(); - let dim_in = dim_out - chain.delta_dim(); - Self { - dim_out, - dim_in, - len_out, - len_in, - chain, - } - } - pub fn identity(dim: usize, len: usize) -> Self { - Self { - dim_out: dim, - dim_in: dim, - len_out: len, - len_in: len, - chain: Vec::new(), - } - } - pub fn push(&mut self, item: Elementary) { - assert!(item.dim_out() <= self.dim_in); - assert_eq!(self.len_in % item.mod_out(), 0); - self.dim_in -= item.delta_dim(); - self.len_in = self.len_in / item.mod_out() * item.mod_in(); - self.chain.push(item); - } - pub fn partial_relative_to(&self, target: &Self) -> Option<(Self, Option>)> { - let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); - let rel = Chain::new(target.dim_in(), target.len_in(), rel.into()); - if rem.dim_out() != 0 { - None - } else if rem.is_identity() { - Some((rel, None)) - } else { - let mut rem_indices: Vec = (0..).take(target.len_in()).collect(); - rem.apply_indices_inplace(&mut rem_indices); - let rel_indices = rel.unapply_indices_unchecked(&rem_indices); - Some((rel, Some(rel_indices))) - } - } -} - -impl Mapping for Chain { - fn len_in(&self) -> usize { - self.len_in - } - fn len_out(&self) -> usize { - self.len_out - } - fn dim_in(&self) -> usize { - self.dim_in - } - fn dim_out(&self) -> usize { - self.dim_out - } - fn delta_dim(&self) -> usize { - self.dim_out - self.dim_in - } - fn add_offset(&mut self, offset: usize) { - self.chain.add_offset(offset); - self.dim_out += offset; - self.dim_in += offset; - } - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - self.chain.apply_inplace(index, coordinates, self.dim_out) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - self.chain.apply_index(index) - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.chain.apply_indices_inplace(indices) - } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { - self.chain.unapply_indices(indices) - } - fn is_identity(&self) -> bool { - self.chain.is_identity() - } -} - -//impl Mul for Chain { -// type Output = Self; -// -// fn mul(self, mut other: Self) -> Self { -// let dim_out = self.dim_out() + other.dim_out(); -// let len_out = self.len_out() * other.len_out(); -// let trans1 = Elementary::new_transpose(other.len_out(), self.len_out()); -// let trans2 = Elementary::new_transpose(self.len_in(), other.len_out()); -// other.add_offset(self.dim_in()); -// let chain: Vec = iter::once(trans1) -// .chain(self.chain) -// .chain(iter::once(trans2)) -// .chain(other.chain) -// .collect(); -// Chain::new(dim_out, len_out, chain) -// } -//} - -#[derive(Debug, Clone)] -struct Concat { - dim_in: usize, - delta_dim: usize, - len_out: usize, - len_in: usize, - items: Vec, -} - -impl Concat { - pub fn new(items: Vec) -> Self { - // TODO: Return `Result`. - let first = items.first().unwrap(); - let dim_in = first.dim_in(); - let delta_dim = first.delta_dim(); - let len_out = first.len_out(); - let mut len_in = 0; - for item in items.iter() { - assert_eq!(item.dim_in(), dim_in); - assert_eq!(item.delta_dim(), delta_dim); - assert_eq!(item.len_out(), len_out); - len_in += item.len_in(); - } - Self { - dim_in, - delta_dim, - len_out, - len_in, - items, - } - } - // pub fn relative_to(&self, target: &Self) -> Option { - // let mut parts = Vec::new(); - // let mut map = iter::repeat((0, 0)).take(self.len_in()).collect(); - // for item in self.items.iter() { - // let mut seen: Vec = iter::repeat(false).take(self.len_in()).collect(); - // let mut offset = 0; - // for titem in target.items.iter() { - // if let Some(rel, indices) = item.relative_to(titem) { - // unimplemented!{} - // } - // offset += titem.len_in(); - // } - // if !seen.all(|c| c) { - // return None; - // } - // } - // RelativeParts { - // dim_in: self.dim_in(), - // delta_dim: target.dim_in() - self.dim_in(), - // len_out: target.len_in(), - // map, - // parts, - // } - // } - fn resolve_item_unchecked(&self, mut index: usize) -> (&Item, usize) { - for item in self.items.iter() { - if index < item.len_in() { - return (item, index); - } - index -= item.len_in(); - } - panic!("index out of range"); - } -} - -impl Mapping for Concat { - fn dim_in(&self) -> usize { - self.dim_in - } - fn delta_dim(&self) -> usize { - self.delta_dim - } - fn len_out(&self) -> usize { - self.len_out - } - fn len_in(&self) -> usize { - self.len_in - } - fn add_offset(&mut self, offset: usize) { - for item in self.items.iter_mut() { - item.add_offset(offset); - } - self.dim_in += offset; - } - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - let (item, index) = self.resolve_item_unchecked(index); - item.apply_inplace_unchecked(index, coordinates) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - let (item, index) = self.resolve_item_unchecked(index); - item.apply_index_unchecked(index) - } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { - unimplemented! {} - } - fn is_identity(&self) -> bool { - false - } -} - -trait RelativeTo { - type Output: Mapping; - - fn relative_to(&self, target: &Target) -> Option; -} - -impl RelativeTo for Chain { - type Output = Self; - - fn relative_to(&self, target: &Self) -> Option { - let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); - rem.is_identity() - .then(|| Chain::new(target.dim_in(), target.len_in(), rel.into())) - } -} - -type ElemComp = Size>; - -impl RelativeTo for ElemComp { - type Output = Self; - - fn relative_to(&self, target: &Self) -> Option { - let (common, rem, rel) = target.mapping.remove_common_prefix_opt_lhs(&self.mapping); - rem.is_identity() - .then(|| Self::new(rel, target.dim_in(), target.len_in()).unwrap()) - // TODO: Self::new_unchecked - } -} - -impl RelativeTo for Concat -where - Item: Mapping + RelativeTo, - Target: Mapping, -{ - type Output = Concat; - - fn relative_to(&self, target: &Target) -> Option { - self.items - .iter() - .map(|item| item.relative_to(target)) - .collect::>() - .map(|rel_items| Concat::new(rel_items)) - } -} - -//impl RelativeTo> for Chain { -// type Output = RelativeToConcatChain; -// -// fn relative_to(&self, targets: &Concat) -> Option { -// let mut rels_indices = Vec::new(); -// let mut offset = 0; -// for (itarget, target) in targets.items.iter().enumerate() { -// let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); -// // if rem.is_identity() { -// // return rel + offset; -// // } -// if rem.dim_out() == 0 { -// let mut rem_indices: Vec = (0..).take(target.len_in()).collect(); -// rem.apply_indices_inplace(&mut rem_indices); -// // TODO: First collect rem_indices for all targets, then remove -// // the common tail from all rels and then unapply the rem -// // indices on the rels. -// rels_indices.push((rel, itarget, rem_indices, offset)) -// } -// offset += target.len_in(); -// } -// // TODO: Split off common tail. -// // TODO: unapply indices and build map -// // let rel_indices = rel.unapply_indices_unchecked(&rem_indices); -// // if !rel_indices.is_empty() { -// // // update map -// // //tails.push((rel, offset, rel_indices)); -// // } -// // TODO: return None if we didn't find everything -// unimplemented! {} -// // RelativeToConcatChain { ... } -// // Pair>>, Chain> -// } -//} - -#[cfg(test)] -mod tests { - use super::*; - use crate::simplex::Simplex::*; - use approx::assert_abs_diff_eq; - - macro_rules! chain { - (dim=$dim_out:literal, len=$len_out:literal) => { - Chain::identity($dim_out, $len_out) - }; - (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { - Chain::new($dim_out, $len_out, vec![$(Elementary::from($item)),*]) - }; - } - - macro_rules! assert_equiv_chains { - ($a:expr, $b:expr $(, $simplex:ident)*) => {{ - let a = $a; - let b = $b; - println!("a: {a:?}"); - println!("b: {b:?}"); - // Build coords: the outer product of the vertices of the given simplices, zero-padded - // to the dimension of the root. - let coords = iter::once([]); - let simplex_dim = 0; - $( - let coords = coords.flat_map(|coord| { - $simplex - .vertices() - .chunks($simplex.dim()) - .map(move |vert| [&coord, vert].concat()) - }); - let simplex_dim = simplex_dim + $simplex.dim(); - )* - assert_eq!(simplex_dim, a.dim_in(), "given simplices don't add up to the input dimension"); - let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); - let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); - // Test if every input maps to the same output for both `a` and `b`. - for i in 0..2 * a.len_in() { - let mut crds_a = coords.clone(); - let mut crds_b = coords.clone(); - let ja = a.apply_inplace(i, &mut crds_a); - let jb = b.apply_inplace(i, &mut crds_b); - assert_eq!(ja, jb, "i={i}"); - assert_abs_diff_eq!(crds_a[..], crds_b[..]); - } - }}; - } - - macro_rules! assert_partial_relative_to { - ($a:expr, $b:expr $(, $simplex:ident)*) => {{ - let a = $a; - let b = $b; - let (rel, indices) = b.partial_relative_to(&a).unwrap(); - - let (rel_compressed, b_compressed) = if let Some(indices) = indices { - let b_indices: Vec<_> = (0..).take(b.len_in()).filter_map(|i| indices.contains(&i).then(|| i)).collect(); - let mut tmp: Vec<_> = (0..rel.len_in()).collect(); - tmp.sort_by_key(|&i| &indices[i]); - let mut rel_indices: Vec<_> = (0..rel.len_in()).collect(); - rel_indices.sort_by_key(|&i| &tmp[i]); - let mut b = b.clone(); - b.push(Elementary::new_take(b_indices, b.len_in())); - let mut rel = rel.clone(); - rel.push(Elementary::new_take(rel_indices, rel.len_in())); - (rel, b) - } else { - (rel, b) - }; - - assert_equiv_chains!(rel_compressed.compose(a).unwrap(), &b_compressed $(, $simplex)*); - }}; - } - - #[test] - fn partial_relative_to() { - use crate::elementary::*; - assert_partial_relative_to!( - chain!(dim=1, len=2 <- Children::new(Line), Take::new([0,3,1], 4)), - chain!(dim=1, len=2 <- Children::new(Line), Children::new(Line)), - Line - ); - assert_partial_relative_to!( - chain!(dim=0, len=4 <- Take::new([0,3,1], 4)), - chain!(dim = 0, len = 4) - ); - assert_partial_relative_to!( - chain!(dim=1, len=2 <- Children::new(Line)), - chain!(dim=1, len=2 <- Edges::new(Line)) - ); - } - - #[test] - fn rel_to() { - let a = Size::new(vec![Elementary::new_children(Line)], 1, 2).unwrap(); - let b = Size::new(vec![Elementary::new_children(Line), Elementary::new_children(Line)], 1, 2).unwrap(); - assert_eq!(b.relative_to(&a), Some(Size::new(vec![Elementary::new_children(Line)], 1, 4).unwrap())); - } -} From a4b6d7358613044faa6f5fb8fdfc2310c532ca20 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Mon, 20 Jun 2022 22:52:20 +0200 Subject: [PATCH 14/45] WIP --- src/finite.rs | 177 ++++++++++++++++++++++++++----------- src/infinite.rs | 94 ++++++++++++++++---- src/lib.rs | 4 +- src/relative_to.rs | 211 +++++++++++++++++++++------------------------ 4 files changed, 310 insertions(+), 176 deletions(-) diff --git a/src/finite.rs b/src/finite.rs index 2a7bc0eee..fdf4b4bd6 100644 --- a/src/finite.rs +++ b/src/finite.rs @@ -1,6 +1,6 @@ -use crate::infinite::InfiniteMapping; +use crate::infinite::{InfiniteMap, Elementary}; -pub trait Mapping { +pub trait Map { fn len_out(&self) -> usize; fn len_in(&self) -> usize; fn dim_out(&self) -> usize { @@ -59,25 +59,25 @@ impl std::fmt::Display for ComposeCodomainDomainMismatch { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, - "The codomain of the first maping doesn't match the domain of the second mapping," + "The codomain of the first map doesn't match the domain of the second map," ) } } #[derive(Debug, Clone, PartialEq)] -pub struct Composition(M1, M2); +pub struct Composition(M1, M2); -impl Composition { - pub fn new(mapping1: M1, mapping2: M2) -> Result { - if mapping1.len_out() == mapping2.len_in() && mapping1.dim_out() == mapping2.dim_in() { - Ok(Self(mapping1, mapping2)) +impl Composition { + pub fn new(map1: M1, map2: M2) -> Result { + if map1.len_out() == map2.len_in() && map1.dim_out() == map2.dim_in() { + Ok(Self(map1, map2)) } else { Err(ComposeCodomainDomainMismatch) } } } -impl Mapping for Composition { +impl Map for Composition { fn len_in(&self) -> usize { self.0.len_in() } @@ -115,16 +115,19 @@ impl Mapping for Composition { } } -pub trait Compose: Mapping + Sized { - fn compose(self, rhs: Rhs) -> Result, ComposeCodomainDomainMismatch> { +pub trait Compose: Map + Sized { + fn compose( + self, + rhs: Rhs, + ) -> Result, ComposeCodomainDomainMismatch> { Composition::new(self, rhs) } } -impl Compose for M {} +impl Compose for M {} #[derive(Debug, Clone, PartialEq)] -pub struct Concatenation { +pub struct Concatenation { dim_in: usize, delta_dim: usize, len_out: usize, @@ -132,7 +135,7 @@ pub struct Concatenation { items: Vec, } -impl Concatenation { +impl Concatenation { pub fn new(items: Vec) -> Self { // TODO: Return `Result`. let first = items.first().unwrap(); @@ -168,7 +171,7 @@ impl Concatenation { } } -impl Mapping for Concatenation { +impl Map for Concatenation { fn dim_in(&self) -> usize { self.dim_in } @@ -204,56 +207,59 @@ impl Mapping for Concatenation { } #[derive(Debug, Clone, Copy, PartialEq)] -pub enum NewBoundedError { +pub enum NewWithBoundsError { DimensionTooSmall, LengthNotAMultipleOfRepetition, } -impl std::error::Error for NewBoundedError {} +impl std::error::Error for NewWithBoundsError {} -impl std::fmt::Display for NewBoundedError { +impl std::fmt::Display for NewWithBoundsError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - Self::DimensionTooSmall => write!(f, "The dimension of the sized mapping is smaller than the minimum dimension of the unsized mapping."), - Self::LengthNotAMultipleOfRepetition => write!(f, "The length of the sized mapping is not a multiple of the repetition length of the unsized mapping."), + Self::DimensionTooSmall => write!(f, "The dimension of the sized map is smaller than the minimum dimension of the unsized map."), + Self::LengthNotAMultipleOfRepetition => write!(f, "The length of the sized map is not a multiple of the repetition length of the unsized map."), } } } #[derive(Debug, Clone, PartialEq)] -pub struct Bounded { - mapping: M, +pub struct WithBounds { + map: M, delta_dim: usize, dim_in: usize, len_out: usize, len_in: usize, } -impl Bounded { - pub fn new(mapping: M, dim_out: usize, len_out: usize) -> Result { - if dim_out < mapping.dim_out() { - Err(NewBoundedError::DimensionTooSmall) - } else if len_out % mapping.mod_out() != 0 { - Err(NewBoundedError::LengthNotAMultipleOfRepetition) +impl WithBounds { + pub fn new(map: M, dim_out: usize, len_out: usize) -> Result { + if dim_out < map.dim_out() { + Err(NewWithBoundsError::DimensionTooSmall) + } else if len_out % map.mod_out() != 0 { + Err(NewWithBoundsError::LengthNotAMultipleOfRepetition) } else { - let delta_dim = mapping.delta_dim(); - let dim_in = dim_out - delta_dim; - let len_in = len_out / mapping.mod_out() * mapping.mod_in(); - Ok(Self { - mapping, - delta_dim, - dim_in, - len_out, - len_in, - }) + Ok(Self::new_unchecked(map, dim_out, len_out)) } } - pub fn get_unsized(&self) -> &M { - &self.mapping + pub(crate) fn new_unchecked(map: M, dim_out: usize, len_out: usize) -> Self { + let delta_dim = map.delta_dim(); + let dim_in = dim_out - delta_dim; + let len_in = len_out / map.mod_out() * map.mod_in(); + Self { + map, + delta_dim, + dim_in, + len_out, + len_in, + } + } + pub fn get_infinite(&self) -> &M { + &self.map } } -impl Mapping for Bounded { +impl Map for WithBounds { fn len_in(&self) -> usize { self.len_in } @@ -267,23 +273,98 @@ impl Mapping for Bounded { self.delta_dim } fn add_offset(&mut self, offset: usize) { - self.mapping.add_offset(offset); + self.map.add_offset(offset); self.dim_in += offset; } fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - self.mapping - .apply_inplace(index, coordinates, self.dim_out()) + self.map.apply_inplace(index, coordinates, self.dim_out()) } fn apply_index_unchecked(&self, index: usize) -> usize { - self.mapping.apply_index(index) + self.map.apply_index(index) } fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.mapping.apply_indices_inplace(indices) + self.map.apply_indices_inplace(indices) } fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { - self.mapping.unapply_indices(indices) + self.map.unapply_indices(indices) } fn is_identity(&self) -> bool { - self.mapping.is_identity() + self.map.is_identity() + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ConcreteMap { + Elementary(WithBounds>), + Composition(Box>), + Concatenation(Concatenation), +} + +impl ConcreteMap { + pub fn compose(self, other: Self) -> Result { + Ok(Composition::new(self, other)?.into()) + } + pub fn new_concatenation(items: Vec) -> Self { + // TODO: unravel nested concatenations + Self::Concatenation(Concatenation::new(items)) + } +} + +macro_rules! dispatch { + ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { + match $self { + ConcreteMap::Elementary(var) => var.$fn($($arg),*), + ConcreteMap::Composition(var) => var.$fn($($arg),*), + ConcreteMap::Concatenation(var) => var.$fn($($arg),*), + } + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { + match $self { + ConcreteMap::Elementary(var) => var.$fn($($arg),*), + ConcreteMap::Composition(var) => var.$fn($($arg),*), + ConcreteMap::Concatenation(var) => var.$fn($($arg),*), + } + } + }; +} + +impl Map for ConcreteMap { + dispatch!{fn len_out(&self) -> usize} + dispatch!{fn len_in(&self) -> usize} + dispatch!{fn dim_out(&self) -> usize} + dispatch!{fn dim_in(&self) -> usize} + dispatch!{fn delta_dim(&self) -> usize} + dispatch!{fn add_offset(&mut self, offset: usize)} + dispatch!{fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize} + dispatch!{fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option} + dispatch!{fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch!{fn apply_index(&self, index: usize) -> Option} + dispatch!{fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch!{fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch!{fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec} + dispatch!{fn unapply_indices(&self, indices: &[usize]) -> Option>} + dispatch!{fn is_identity(&self) -> bool} +} + +impl From>> for ConcreteMap { + fn from(map: WithBounds>) -> Self { + ConcreteMap::Elementary(map) + } +} + +impl From> for ConcreteMap { + fn from(map: Composition) -> Self { + ConcreteMap::Composition(Box::new(map)) + } +} + +impl From> for ConcreteMap { + fn from(map: Concatenation) -> Self { + ConcreteMap::Concatenation(map) } } diff --git a/src/infinite.rs b/src/infinite.rs index c123790f6..ee656bd39 100644 --- a/src/infinite.rs +++ b/src/infinite.rs @@ -1,13 +1,13 @@ use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; use num::Integer as _; -use std::rc::Rc; use std::ops::{Deref, DerefMut}; +use std::rc::Rc; -pub trait InfiniteMapping { +pub trait InfiniteMap { // Minimum dimension of the input coordinate. If the dimension of the input - // coordinate of [InfiniteMapping::apply()] is larger than the minimum, then - // the mapping of the surplus is the identity mapping. + // coordinate of [InfiniteMap::apply()] is larger than the minimum, then + // the map of the surplus is the identity map. fn dim_in(&self) -> usize; // Minimum dimension of the output coordinate. fn dim_out(&self) -> usize { @@ -16,7 +16,7 @@ pub trait InfiniteMapping { // Difference in dimension of the output and input coordinate. fn delta_dim(&self) -> usize; fn add_offset(&mut self, offset: usize); - // Modulus of the input index. The mapping repeats itself at index `mod_in` + // Modulus of the input index. The map repeats itself at index `mod_in` // and the output index is incremented with `in_index / mod_in * mod_out`. fn mod_in(&self) -> usize; // Modulus if the output index. @@ -70,7 +70,7 @@ impl Transpose { } } -impl InfiniteMapping for Transpose { +impl InfiniteMap for Transpose { fn dim_in(&self) -> usize { 0 } @@ -127,7 +127,7 @@ impl Take { } } -impl InfiniteMapping for Take { +impl InfiniteMap for Take { fn dim_in(&self) -> usize { 0 } @@ -162,6 +162,57 @@ impl InfiniteMapping for Take { } } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Slice { + start: usize, + len_in: usize, + len_out: usize, +} + +impl Slice { + pub fn new(start: usize, len_in: usize, len_out: usize) -> Self { + assert!(len_out >= start + len_in); + Slice { + start, + len_in, + len_out, + } + } +} + +impl InfiniteMap for Slice { + fn dim_in(&self) -> usize { + 0 + } + fn delta_dim(&self) -> usize { + 0 + } + fn add_offset(&mut self, _offset: usize) {} + fn mod_in(&self) -> usize { + self.len_in + } + fn mod_out(&self) -> usize { + self.len_out + } + fn apply_inplace(&self, index: usize, _coordinates: &mut [f64], _stride: usize) -> usize { + self.apply_index(index) + } + fn apply_index(&self, index: usize) -> usize { + self.start + index % self.len_in + index / self.len_in * self.len_out + } + fn unapply_indices(&self, indices: &[usize]) -> Vec { + indices + .iter() + .filter_map(|index| { + let (j, i) = divmod(*index, self.len_out); + (self.start..self.start + self.len_in) + .contains(&i) + .then(|| i - self.start + j * self.len_in) + }) + .collect() + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Children(Simplex, usize); @@ -171,7 +222,7 @@ impl Children { } } -impl InfiniteMapping for Children { +impl InfiniteMap for Children { fn dim_in(&self) -> usize { self.0.dim() + self.1 } @@ -217,7 +268,7 @@ impl Edges { } } -impl InfiniteMapping for Edges { +impl InfiniteMap for Edges { fn dim_in(&self) -> usize { self.0.edge_dim() + self.1 } @@ -277,7 +328,7 @@ impl UniformPoints { } } -impl InfiniteMapping for UniformPoints { +impl InfiniteMap for UniformPoints { fn dim_in(&self) -> usize { self.offset } @@ -317,6 +368,7 @@ impl InfiniteMapping for UniformPoints { pub enum Elementary { Transpose(Transpose), Take(Take), + Slice(Slice), Children(Children), Edges(Edges), UniformPoints(UniformPoints), @@ -332,6 +384,10 @@ impl Elementary { Self::Take(Take::new(indices, len)) } #[inline] + pub fn new_slice(start: usize, len_in: usize, len_out: usize) -> Self { + Self::Slice(Slice::new(start, len_in, len_out)) + } + #[inline] pub fn new_children(simplex: Simplex) -> Self { Self::Children(Children::new(simplex)) } @@ -476,6 +532,7 @@ macro_rules! dispatch { match $self { Elementary::Transpose(var) => var.$fn($($arg),*), Elementary::Take(var) => var.$fn($($arg),*), + Elementary::Slice(var) => var.$fn($($arg),*), Elementary::Children(var) => var.$fn($($arg),*), Elementary::Edges(var) => var.$fn($($arg),*), Elementary::UniformPoints(var) => var.$fn($($arg),*), @@ -488,6 +545,7 @@ macro_rules! dispatch { match $self { Elementary::Transpose(var) => var.$fn($($arg),*), Elementary::Take(var) => var.$fn($($arg),*), + Elementary::Slice(var) => var.$fn($($arg),*), Elementary::Children(var) => var.$fn($($arg),*), Elementary::Edges(var) => var.$fn($($arg),*), Elementary::UniformPoints(var) => var.$fn($($arg),*), @@ -496,7 +554,7 @@ macro_rules! dispatch { }; } -impl InfiniteMapping for Elementary { +impl InfiniteMap for Elementary { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn add_offset(&mut self, offset: usize)} @@ -521,6 +579,12 @@ impl From for Elementary { } } +impl From for Elementary { + fn from(slice: Slice) -> Self { + Self::Slice(slice) + } +} + impl From for Elementary { fn from(children: Children) -> Self { Self::Children(children) @@ -539,7 +603,7 @@ impl From for Elementary { } } -fn dim_out_in(items: &[M]) -> (usize, usize) { +fn dim_out_in(items: &[M]) -> (usize, usize) { let mut dim_in = 0; let mut dim_out = 0; for item in items.iter().rev() { @@ -552,7 +616,7 @@ fn dim_out_in(items: &[M]) -> (usize, usize) { (dim_out, dim_in) } -fn mod_out_in(items: &[M]) -> (usize, usize) { +fn mod_out_in(items: &[M]) -> (usize, usize) { let mut mod_out = 1; let mut mod_in = 1; for item in items.iter().rev() { @@ -564,9 +628,9 @@ fn mod_out_in(items: &[M]) -> (usize, usize) { } /// Composition. -impl InfiniteMapping for Array +impl InfiniteMap for Array where - Item: InfiniteMapping, + Item: InfiniteMap, Array: Deref + DerefMut, { fn dim_in(&self) -> usize { diff --git a/src/lib.rs b/src/lib.rs index a60404fce..c9a59e789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ +pub mod finite; pub mod finite_f64; -pub mod simplex; pub mod infinite; -pub mod finite; pub mod relative_to; +pub mod simplex; diff --git a/src/relative_to.rs b/src/relative_to.rs index 754fbc984..02ca8a835 100644 --- a/src/relative_to.rs +++ b/src/relative_to.rs @@ -1,17 +1,16 @@ -use crate::finite::Mapping; -use crate::infinite::{InfiniteMapping, Edges, Elementary, Transpose}; +use crate::finite::Map; +use crate::finite::{WithBounds, Compose, Composition, Concatenation, ConcreteMap}; +use crate::infinite::{Edges, Elementary, InfiniteMap, Transpose}; use crate::simplex::Simplex; use std::collections::BTreeMap; -use crate::finite::{Bounded, Concatenation, Composition, Compose}; +use std::iter; trait RemoveCommonPrefix: Sized { fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self); fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self); } -fn split_heads( - items: &[Elementary], -) -> BTreeMap, Vec)> { +fn split_heads(items: &[Elementary]) -> BTreeMap, Vec)> { let mut heads = BTreeMap::new(); for (i, item) in items.iter().enumerate() { if let Some((transpose, head, mut tail)) = item.shift_left(&items[..i]) { @@ -76,10 +75,8 @@ impl RemoveCommonPrefix for Vec { (common, tail1, tail2) } fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self) { - let (common, tail1, tail2) = self.remove_common_prefix(other); + let (common, mut tail1, mut tail2) = self.remove_common_prefix(other); // Move transposes at the front of `tail1` to `tail2`. - let mut tail1: Vec<_> = tail1.into(); - let mut tail2: Vec<_> = tail2.into(); tail1.reverse(); tail2.reverse(); while let Some(mut item) = tail1.pop() { @@ -97,81 +94,110 @@ impl RemoveCommonPrefix for Vec { } } -trait RelativeTo { - type Output: Mapping; - - fn relative_to(&self, target: &Target) -> Option; +trait RelativeTo { + fn relative_to(&self, target: &Target) -> Option; } -type ElemComp = Bounded>; - -impl RelativeTo for ElemComp { - type Output = Self; - - fn relative_to(&self, target: &Self) -> Option { - let (common, rem, rel) = target.get_unsized().remove_common_prefix_opt_lhs(&self.get_unsized()); +impl RelativeTo for WithBounds> { + fn relative_to(&self, target: &Self) -> Option { + let (common, rem, rel) = target + .get_infinite() + .remove_common_prefix_opt_lhs(&self.get_infinite()); rem.is_identity() - .then(|| Self::new(rel, target.dim_in(), target.len_in()).unwrap()) - // TODO: Self::new_unchecked + .then(|| Self::new_unchecked(rel, target.dim_in(), target.len_in()).into()) } } impl RelativeTo for Concatenation where - Item: Mapping + RelativeTo, - Target: Mapping, + Item: Map + RelativeTo, + Target: Map, { - type Output = Concatenation; - - fn relative_to(&self, target: &Target) -> Option { + fn relative_to(&self, target: &Target) -> Option { self.iter() .map(|item| item.relative_to(target)) .collect::>() - .map(|rel_items| Concatenation::new(rel_items)) + .map(|rel_items| Concatenation::new(rel_items).into()) + } +} + +// trait PartialMakeRelative { +// fn partial_make_relative(&self, target: Target) -> Option, Vec)>>; +// } + +impl RelativeTo> for WithBounds> { + fn relative_to(&self, targets: &Concatenation) -> Option { + let mut rels_indices = Vec::new(); + let mut offset = 0; + for target in targets.iter() { + let (common, rem, rel) = target.get_infinite().remove_common_prefix_opt_lhs(&self.get_infinite()); + let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); + if rem.is_identity() { + let rel: Vec = iter::once(slice).chain(rel).collect(); + let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); + return Some(ConcreteMap::Elementary(rel)); + } + if rem.dim_out() == 0 { + let mut indices: Vec = (0..target.len_in()).collect(); + rem.apply_indices_inplace(&mut indices); + rels_indices.push((rel, slice, indices)) + } + offset += target.len_in(); + } + // TODO: Split off common tail. + let mut concat_indices = Vec::new(); + let mut rels = Vec::new(); + for (irel, (rel, slice, out_indices)) in rels_indices.into_iter().enumerate() { + let in_indices = rel.unapply_indices(&out_indices); + let offset = irel * self.len_in(); + concat_indices.extend(in_indices.into_iter().map(|i| i + offset)); + let rel: Vec = iter::once(slice).chain(rel).collect(); + let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); + let rel = ConcreteMap::Elementary(rel); + rels.push(rel); + } + if concat_indices.len() != self.len_in() { + return None; + } + let take = Elementary::new_take(concat_indices, self.len_in() * rels.len()); + let take: ConcreteMap = WithBounds::new_unchecked(vec![take], self.dim_in(), self.len_in()).into(); + let concat = ConcreteMap::new_concatenation(rels); + Some(take.compose(concat).unwrap().into()) + } +} + +impl RelativeTo for WithBounds> { + fn relative_to(&self, target: &ConcreteMap) -> Option { + match target { + ConcreteMap::Elementary(target) => self.relative_to(target), + ConcreteMap::Concatenation(target) => { + let c: Option>>> = target.iter().map(|item| match item { + ConcreteMap::Elementary(item) => Some(item.clone()), + _ => None, + }).collect(); + c.and_then(|c| self.relative_to(&Concatenation::new(c))) + } + _ => None + } } } -//impl RelativeTo> for ElemComp { -// type Output = Composition>; -// -// fn relative_to(&self, targets: &Concatenation) -> Option { -// let mut rels_indices = Vec::new(); -// let mut offset = 0; -// for (itarget, target) in targets.items.iter().enumerate() { -// let (common, rem, rel) = target.chain.remove_common_prefix_opt_lhs(&self.chain); -// // if rem.is_identity() { -// // return rel + offset; -// // } -// if rem.dim_out() == 0 { -// let mut rem_indices: Vec = (0..).take(target.len_in()).collect(); -// rem.apply_indices_inplace(&mut rem_indices); -// // TODO: First collect rem_indices for all targets, then remove -// // the common tail from all rels and then unapply the rem -// // indices on the rels. -// rels_indices.push((rel, itarget, rem_indices, offset)) -// } -// offset += target.len_in(); -// } -// // TODO: Split off common tail. -// // TODO: unapply indices and build map -// // let rel_indices = rel.unapply_indices_unchecked(&rem_indices); -// // if !rel_indices.is_empty() { -// // // update map -// // //tails.push((rel, offset, rel_indices)); -// // } -// // TODO: return None if we didn't find everything -// unimplemented! {} -// // RelativeToConcatenateChain { ... } -// // Composition> -// } -//} +impl RelativeTo for ConcreteMap { + fn relative_to(&self, target: &Self) -> Option { + match self { + ConcreteMap::Elementary(source) => source.relative_to(target), + ConcreteMap::Concatenation(source) => source.relative_to(target), + _ => None + } + } +} #[cfg(test)] mod tests { use super::*; + use crate::infinite::*; use crate::simplex::Simplex::*; use approx::assert_abs_diff_eq; - use crate::infinite::*; use std::iter; #[test] @@ -193,10 +219,10 @@ mod tests { macro_rules! elem_comp { (dim=$dim_out:literal, len=$len_out:literal) => { - ElemComp::new(Vec::new(), $dim_out, $len_out).unwrap() + WithBounds::>::new(Vec::new(), $dim_out, $len_out).unwrap() }; (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { - ElemComp::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() + WithBounds::>::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() }; } @@ -219,7 +245,7 @@ mod tests { }); let simplex_dim = simplex_dim + $simplex.dim(); )* - assert_eq!(simplex_dim, a.dim_in(), "given simplices don't add up to the input dimension"); + assert_eq!(simplex_dim, a.dim_in(), "the given simplices don't add up to the input dimension"); let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); // Test if every input maps to the same output for both `a` and `b`. @@ -234,52 +260,15 @@ mod tests { }}; } -// macro_rules! assert_partial_relative_to { -// ($a:expr, $b:expr $(, $simplex:ident)*) => {{ -// let a = $a; -// let b = $b; -// let (rel, indices) = b.partial_relative_to(&a).unwrap(); -// -// let (rel_compressed, b_compressed) = if let Some(indices) = indices { -// let b_indices: Vec<_> = (0..).take(b.len_in()).filter_map(|i| indices.contains(&i).then(|| i)).collect(); -// let mut tmp: Vec<_> = (0..rel.len_in()).collect(); -// tmp.sort_by_key(|&i| &indices[i]); -// let mut rel_indices: Vec<_> = (0..rel.len_in()).collect(); -// rel_indices.sort_by_key(|&i| &tmp[i]); -// let mut b = b.clone(); -// b.push(Elementary::new_take(b_indices, b.len_in())); -// let mut rel = rel.clone(); -// rel.push(Elementary::new_take(rel_indices, rel.len_in())); -// (rel, b) -// } else { -// (rel, b) -// }; -// -// assert_equiv_chains!(rel_compressed.compose(a).unwrap(), &b_compressed $(, $simplex)*); -// }}; -// } -// -// #[test] -// fn partial_relative_to() { -// assert_partial_relative_to!( -// elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([0,3,1], 4)), -// elem_comp!(dim=1, len=2 <- Children::new(Line), Children::new(Line)), -// Line -// ); -// assert_partial_relative_to!( -// elem_comp!(dim=0, len=4 <- Take::new([0,3,1], 4)), -// elem_comp!(dim = 0, len = 4) -// ); -// assert_partial_relative_to!( -// elem_comp!(dim=1, len=2 <- Children::new(Line)), -// elem_comp!(dim=1, len=2 <- Edges::new(Line)) -// ); -// } - #[test] fn rel_to() { - let a = elem_comp!(dim=1, len=2 <- Children::new(Line)); - let b = elem_comp!(dim=1, len=2 <- Children::new(Line), Children::new(Line)); - assert_eq!(b.relative_to(&a), Some(elem_comp!(dim=1, len=4 <- Children::new(Line)))); + let a1: ConcreteMap = elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([0, 2], 4)).into(); + let a2: ConcreteMap = elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([1, 3], 4), Children::new(Line)).into(); + let a = ConcreteMap::new_concatenation(vec![a1, a2].into()); + let b: ConcreteMap = elem_comp!(dim=1, len=2 <- Children::new(Line), Children::new(Line)).into(); + assert_equiv_chains!( + b.relative_to(&a).unwrap().compose(a.clone()).unwrap(), + b, + Line); } } From 5b8319f7b16f9589a9f06b89db2fcc45969b8926 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Mon, 20 Jun 2022 22:55:41 +0200 Subject: [PATCH 15/45] WIP --- src/finite.rs | 30 +++++++++++++++--------------- src/infinite.rs | 26 +++++++++++++------------- src/relative_to.rs | 10 +++++----- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/finite.rs b/src/finite.rs index fdf4b4bd6..ace1272f0 100644 --- a/src/finite.rs +++ b/src/finite.rs @@ -1,6 +1,6 @@ -use crate::infinite::{InfiniteMap, Elementary}; +use crate::infinite::{UnboundedMap, Elementary}; -pub trait Map { +pub trait BoundedMap { fn len_out(&self) -> usize; fn len_in(&self) -> usize; fn dim_out(&self) -> usize { @@ -65,9 +65,9 @@ impl std::fmt::Display for ComposeCodomainDomainMismatch { } #[derive(Debug, Clone, PartialEq)] -pub struct Composition(M1, M2); +pub struct Composition(M1, M2); -impl Composition { +impl Composition { pub fn new(map1: M1, map2: M2) -> Result { if map1.len_out() == map2.len_in() && map1.dim_out() == map2.dim_in() { Ok(Self(map1, map2)) @@ -77,7 +77,7 @@ impl Composition { } } -impl Map for Composition { +impl BoundedMap for Composition { fn len_in(&self) -> usize { self.0.len_in() } @@ -115,8 +115,8 @@ impl Map for Composition { } } -pub trait Compose: Map + Sized { - fn compose( +pub trait Compose: BoundedMap + Sized { + fn compose( self, rhs: Rhs, ) -> Result, ComposeCodomainDomainMismatch> { @@ -124,10 +124,10 @@ pub trait Compose: Map + Sized { } } -impl Compose for M {} +impl Compose for M {} #[derive(Debug, Clone, PartialEq)] -pub struct Concatenation { +pub struct Concatenation { dim_in: usize, delta_dim: usize, len_out: usize, @@ -135,7 +135,7 @@ pub struct Concatenation { items: Vec, } -impl Concatenation { +impl Concatenation { pub fn new(items: Vec) -> Self { // TODO: Return `Result`. let first = items.first().unwrap(); @@ -171,7 +171,7 @@ impl Concatenation { } } -impl Map for Concatenation { +impl BoundedMap for Concatenation { fn dim_in(&self) -> usize { self.dim_in } @@ -224,7 +224,7 @@ impl std::fmt::Display for NewWithBoundsError { } #[derive(Debug, Clone, PartialEq)] -pub struct WithBounds { +pub struct WithBounds { map: M, delta_dim: usize, dim_in: usize, @@ -232,7 +232,7 @@ pub struct WithBounds { len_in: usize, } -impl WithBounds { +impl WithBounds { pub fn new(map: M, dim_out: usize, len_out: usize) -> Result { if dim_out < map.dim_out() { Err(NewWithBoundsError::DimensionTooSmall) @@ -259,7 +259,7 @@ impl WithBounds { } } -impl Map for WithBounds { +impl BoundedMap for WithBounds { fn len_in(&self) -> usize { self.len_in } @@ -333,7 +333,7 @@ macro_rules! dispatch { }; } -impl Map for ConcreteMap { +impl BoundedMap for ConcreteMap { dispatch!{fn len_out(&self) -> usize} dispatch!{fn len_in(&self) -> usize} dispatch!{fn dim_out(&self) -> usize} diff --git a/src/infinite.rs b/src/infinite.rs index ee656bd39..2f88d4cdf 100644 --- a/src/infinite.rs +++ b/src/infinite.rs @@ -4,9 +4,9 @@ use num::Integer as _; use std::ops::{Deref, DerefMut}; use std::rc::Rc; -pub trait InfiniteMap { +pub trait UnboundedMap { // Minimum dimension of the input coordinate. If the dimension of the input - // coordinate of [InfiniteMap::apply()] is larger than the minimum, then + // coordinate of [UnboundedMap::apply()] is larger than the minimum, then // the map of the surplus is the identity map. fn dim_in(&self) -> usize; // Minimum dimension of the output coordinate. @@ -70,7 +70,7 @@ impl Transpose { } } -impl InfiniteMap for Transpose { +impl UnboundedMap for Transpose { fn dim_in(&self) -> usize { 0 } @@ -127,7 +127,7 @@ impl Take { } } -impl InfiniteMap for Take { +impl UnboundedMap for Take { fn dim_in(&self) -> usize { 0 } @@ -180,7 +180,7 @@ impl Slice { } } -impl InfiniteMap for Slice { +impl UnboundedMap for Slice { fn dim_in(&self) -> usize { 0 } @@ -222,7 +222,7 @@ impl Children { } } -impl InfiniteMap for Children { +impl UnboundedMap for Children { fn dim_in(&self) -> usize { self.0.dim() + self.1 } @@ -268,7 +268,7 @@ impl Edges { } } -impl InfiniteMap for Edges { +impl UnboundedMap for Edges { fn dim_in(&self) -> usize { self.0.edge_dim() + self.1 } @@ -328,7 +328,7 @@ impl UniformPoints { } } -impl InfiniteMap for UniformPoints { +impl UnboundedMap for UniformPoints { fn dim_in(&self) -> usize { self.offset } @@ -554,7 +554,7 @@ macro_rules! dispatch { }; } -impl InfiniteMap for Elementary { +impl UnboundedMap for Elementary { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn add_offset(&mut self, offset: usize)} @@ -603,7 +603,7 @@ impl From for Elementary { } } -fn dim_out_in(items: &[M]) -> (usize, usize) { +fn dim_out_in(items: &[M]) -> (usize, usize) { let mut dim_in = 0; let mut dim_out = 0; for item in items.iter().rev() { @@ -616,7 +616,7 @@ fn dim_out_in(items: &[M]) -> (usize, usize) { (dim_out, dim_in) } -fn mod_out_in(items: &[M]) -> (usize, usize) { +fn mod_out_in(items: &[M]) -> (usize, usize) { let mut mod_out = 1; let mut mod_in = 1; for item in items.iter().rev() { @@ -628,9 +628,9 @@ fn mod_out_in(items: &[M]) -> (usize, usize) { } /// Composition. -impl InfiniteMap for Array +impl UnboundedMap for Array where - Item: InfiniteMap, + Item: UnboundedMap, Array: Deref + DerefMut, { fn dim_in(&self) -> usize { diff --git a/src/relative_to.rs b/src/relative_to.rs index 02ca8a835..3bbaef882 100644 --- a/src/relative_to.rs +++ b/src/relative_to.rs @@ -1,6 +1,6 @@ -use crate::finite::Map; +use crate::finite::BoundedMap; use crate::finite::{WithBounds, Compose, Composition, Concatenation, ConcreteMap}; -use crate::infinite::{Edges, Elementary, InfiniteMap, Transpose}; +use crate::infinite::{Edges, Elementary, UnboundedMap, Transpose}; use crate::simplex::Simplex; use std::collections::BTreeMap; use std::iter; @@ -94,7 +94,7 @@ impl RemoveCommonPrefix for Vec { } } -trait RelativeTo { +trait RelativeTo { fn relative_to(&self, target: &Target) -> Option; } @@ -110,8 +110,8 @@ impl RelativeTo for WithBounds> { impl RelativeTo for Concatenation where - Item: Map + RelativeTo, - Target: Map, + Item: BoundedMap + RelativeTo, + Target: BoundedMap, { fn relative_to(&self, target: &Target) -> Option { self.iter() From ec799b6cd3c2ef8e1dd000174c0df8a33e897536 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 22 Jun 2022 11:09:23 +0200 Subject: [PATCH 16/45] WIP --- src/finite.rs | 89 +------- src/infinite.rs | 78 ++++--- src/lib.rs | 16 ++ src/relative_to.rs | 512 +++++++++++++++++++++++++++++---------------- 4 files changed, 393 insertions(+), 302 deletions(-) diff --git a/src/finite.rs b/src/finite.rs index ace1272f0..ea4537f83 100644 --- a/src/finite.rs +++ b/src/finite.rs @@ -1,4 +1,5 @@ use crate::infinite::{UnboundedMap, Elementary}; +use crate::UnapplyIndicesData; pub trait BoundedMap { fn len_out(&self) -> usize; @@ -39,9 +40,9 @@ pub trait BoundedMap { None } } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec; - fn unapply_indices(&self, indices: &[usize]) -> Option> { - if indices.iter().all(|index| *index < self.len_out()) { + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec; + fn unapply_indices(&self, indices: &[T]) -> Option> { + if indices.iter().all(|index| index.last() < self.len_out()) { Some(self.unapply_indices_unchecked(indices)) } else { None @@ -106,7 +107,7 @@ impl BoundedMap for Composition { self.0.apply_indices_inplace_unchecked(indices); self.1.apply_indices_inplace_unchecked(indices); } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { let indices = self.1.unapply_indices_unchecked(indices); self.0.unapply_indices_unchecked(&indices) } @@ -198,7 +199,7 @@ impl BoundedMap for Concatenation { let (item, index) = self.resolve_item_unchecked(index); item.apply_index_unchecked(index) } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { unimplemented! {} } fn is_identity(&self) -> bool { @@ -285,86 +286,10 @@ impl BoundedMap for WithBounds { fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { self.map.apply_indices_inplace(indices) } - fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec { + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { self.map.unapply_indices(indices) } fn is_identity(&self) -> bool { self.map.is_identity() } } - -#[derive(Debug, Clone, PartialEq)] -pub enum ConcreteMap { - Elementary(WithBounds>), - Composition(Box>), - Concatenation(Concatenation), -} - -impl ConcreteMap { - pub fn compose(self, other: Self) -> Result { - Ok(Composition::new(self, other)?.into()) - } - pub fn new_concatenation(items: Vec) -> Self { - // TODO: unravel nested concatenations - Self::Concatenation(Concatenation::new(items)) - } -} - -macro_rules! dispatch { - ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { - #[inline] - $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { - match $self { - ConcreteMap::Elementary(var) => var.$fn($($arg),*), - ConcreteMap::Composition(var) => var.$fn($($arg),*), - ConcreteMap::Concatenation(var) => var.$fn($($arg),*), - } - } - }; - ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { - #[inline] - $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { - match $self { - ConcreteMap::Elementary(var) => var.$fn($($arg),*), - ConcreteMap::Composition(var) => var.$fn($($arg),*), - ConcreteMap::Concatenation(var) => var.$fn($($arg),*), - } - } - }; -} - -impl BoundedMap for ConcreteMap { - dispatch!{fn len_out(&self) -> usize} - dispatch!{fn len_in(&self) -> usize} - dispatch!{fn dim_out(&self) -> usize} - dispatch!{fn dim_in(&self) -> usize} - dispatch!{fn delta_dim(&self) -> usize} - dispatch!{fn add_offset(&mut self, offset: usize)} - dispatch!{fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize} - dispatch!{fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option} - dispatch!{fn apply_index_unchecked(&self, index: usize) -> usize} - dispatch!{fn apply_index(&self, index: usize) -> Option} - dispatch!{fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} - dispatch!{fn apply_indices(&self, indices: &[usize]) -> Option>} - dispatch!{fn unapply_indices_unchecked(&self, indices: &[usize]) -> Vec} - dispatch!{fn unapply_indices(&self, indices: &[usize]) -> Option>} - dispatch!{fn is_identity(&self) -> bool} -} - -impl From>> for ConcreteMap { - fn from(map: WithBounds>) -> Self { - ConcreteMap::Elementary(map) - } -} - -impl From> for ConcreteMap { - fn from(map: Composition) -> Self { - ConcreteMap::Composition(Box::new(map)) - } -} - -impl From> for ConcreteMap { - fn from(map: Concatenation) -> Self { - ConcreteMap::Concatenation(map) - } -} diff --git a/src/infinite.rs b/src/infinite.rs index 2f88d4cdf..26cc207d5 100644 --- a/src/infinite.rs +++ b/src/infinite.rs @@ -1,4 +1,5 @@ use crate::finite_f64::FiniteF64; +use crate::UnapplyIndicesData; use crate::simplex::Simplex; use num::Integer as _; use std::ops::{Deref, DerefMut}; @@ -28,7 +29,7 @@ pub trait UnboundedMap { *index = self.apply_index(*index); } } - fn unapply_indices(&self, indices: &[usize]) -> Vec; + fn unapply_indices(&self, indices: &[T]) -> Vec; fn is_identity(&self) -> bool { self.mod_in() == 1 && self.mod_out() == 1 && self.dim_out() == 0 } @@ -96,13 +97,13 @@ impl UnboundedMap for Transpose { let (i, j) = divmod(j, self.0); (i * self.1 + k) * self.0 + j } - fn unapply_indices(&self, indices: &[usize]) -> Vec { + fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() - .map(|k| { - let (j, k) = divmod(*k, self.0); + .map(|index| { + let (j, k) = divmod(index.last(), self.0); let (i, j) = divmod(j, self.1); - (i * self.0 + k) * self.1 + j + index.push((i * self.0 + k) * self.1 + j) }) .collect() } @@ -147,16 +148,16 @@ impl UnboundedMap for Take { fn apply_index(&self, index: usize) -> usize { self.indices[index % self.nindices] + index / self.nindices * self.len } - fn unapply_indices(&self, indices: &[usize]) -> Vec { + fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() .filter_map(|index| { - let (j, iout) = divmod(*index, self.len); + let (j, iout) = divmod(index.last(), self.len); let offset = j * self.nindices; self.indices .iter() .position(|i| *i == iout) - .map(|iin| offset + iin) + .map(|iin| index.push(offset + iin)) }) .collect() } @@ -200,14 +201,14 @@ impl UnboundedMap for Slice { fn apply_index(&self, index: usize) -> usize { self.start + index % self.len_in + index / self.len_in * self.len_out } - fn unapply_indices(&self, indices: &[usize]) -> Vec { + fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() .filter_map(|index| { - let (j, i) = divmod(*index, self.len_out); + let (j, i) = divmod(index.last(), self.len_out); (self.start..self.start + self.len_in) .contains(&i) - .then(|| i - self.start + j * self.len_in) + .then(|| index.push(i - self.start + j * self.len_in)) }) .collect() } @@ -244,11 +245,10 @@ impl UnboundedMap for Children { fn apply_index(&self, index: usize) -> usize { self.0.apply_child_index(index) } - fn unapply_indices(&self, indices: &[usize]) -> Vec { + fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() - .map(|i| i * self.mod_in()) - .flat_map(|i| (0..self.mod_in()).map(move |j| i + j)) + .flat_map(|i| (0..self.mod_in()).map(move |j| i.push(i.last() * self.mod_in() + j))) .collect() } } @@ -290,11 +290,10 @@ impl UnboundedMap for Edges { fn apply_index(&self, index: usize) -> usize { self.0.apply_edge_index(index) } - fn unapply_indices(&self, indices: &[usize]) -> Vec { + fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() - .map(|i| i * self.mod_in()) - .flat_map(|i| (0..self.mod_in()).map(move |j| i + j)) + .flat_map(|i| (0..self.mod_in()).map(move |j| i.push(i.last() * self.mod_in() + j))) .collect() } } @@ -355,11 +354,10 @@ impl UnboundedMap for UniformPoints { fn apply_index(&self, index: usize) -> usize { index / self.npoints } - fn unapply_indices(&self, indices: &[usize]) -> Vec { + fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() - .map(|i| i * self.mod_in()) - .flat_map(|i| (0..self.mod_in()).map(move |j| i + j)) + .flat_map(|i| (0..self.mod_in()).map(move |j| i.push(i.last() * self.mod_in() + j))) .collect() } } @@ -526,30 +524,30 @@ impl Elementary { } macro_rules! dispatch { - ($vis:vis fn $fn:ident(&$self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $($ret:tt)* + ) => { #[inline] - $vis fn $fn(&$self $(, $arg: $ty)*) $($ret)* { - match $self { - Elementary::Transpose(var) => var.$fn($($arg),*), - Elementary::Take(var) => var.$fn($($arg),*), - Elementary::Slice(var) => var.$fn($($arg),*), - Elementary::Children(var) => var.$fn($($arg),*), - Elementary::Edges(var) => var.$fn($($arg),*), - Elementary::UniformPoints(var) => var.$fn($($arg),*), - } + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $($ret)* { + dispatch!(@match $self; $fn; $($arg),*) } }; ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { #[inline] $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { - match $self { - Elementary::Transpose(var) => var.$fn($($arg),*), - Elementary::Take(var) => var.$fn($($arg),*), - Elementary::Slice(var) => var.$fn($($arg),*), - Elementary::Children(var) => var.$fn($($arg),*), - Elementary::Edges(var) => var.$fn($($arg),*), - Elementary::UniformPoints(var) => var.$fn($($arg),*), - } + dispatch!(@match $self; $fn; $($arg),*) + } + }; + (@match $self:ident; $fn:ident; $($arg:ident),*) => { + match $self { + Elementary::Transpose(var) => var.$fn($($arg),*), + Elementary::Take(var) => var.$fn($($arg),*), + Elementary::Slice(var) => var.$fn($($arg),*), + Elementary::Children(var) => var.$fn($($arg),*), + Elementary::Edges(var) => var.$fn($($arg),*), + Elementary::UniformPoints(var) => var.$fn($($arg),*), } }; } @@ -563,7 +561,7 @@ impl UnboundedMap for Elementary { dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize} dispatch! {fn apply_index(&self, index: usize) -> usize} dispatch! {fn apply_indices_inplace(&self, indices: &mut [usize])} - dispatch! {fn unapply_indices(&self, indices: &[usize]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Vec} dispatch! {fn is_identity(&self) -> bool} } @@ -665,7 +663,7 @@ where item.apply_indices_inplace(indices); } } - fn unapply_indices(&self, indices: &[usize]) -> Vec { + fn unapply_indices(&self, indices: &[T]) -> Vec { self.iter().fold(indices.to_vec(), |indices, item| { item.unapply_indices(&indices) }) diff --git a/src/lib.rs b/src/lib.rs index c9a59e789..9be12708f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,3 +3,19 @@ pub mod finite_f64; pub mod infinite; pub mod relative_to; pub mod simplex; + +pub trait UnapplyIndicesData: Clone { + fn last(&self) -> usize; + fn push(&self, index: usize) -> Self; +} + +impl UnapplyIndicesData for usize { + #[inline] + fn last(&self) -> usize { + *self + } + #[inline] + fn push(&self, index: usize) -> Self { + index + } +} diff --git a/src/relative_to.rs b/src/relative_to.rs index 3bbaef882..da231e83f 100644 --- a/src/relative_to.rs +++ b/src/relative_to.rs @@ -1,7 +1,7 @@ -use crate::finite::BoundedMap; -use crate::finite::{WithBounds, Compose, Composition, Concatenation, ConcreteMap}; +use crate::finite::{BoundedMap, WithBounds, Compose, Composition, Concatenation, ComposeCodomainDomainMismatch}; use crate::infinite::{Edges, Elementary, UnboundedMap, Transpose}; use crate::simplex::Simplex; +use crate::UnapplyIndicesData; use std::collections::BTreeMap; use std::iter; @@ -94,181 +94,333 @@ impl RemoveCommonPrefix for Vec { } } -trait RelativeTo { - fn relative_to(&self, target: &Target) -> Option; -} - -impl RelativeTo for WithBounds> { - fn relative_to(&self, target: &Self) -> Option { - let (common, rem, rel) = target - .get_infinite() - .remove_common_prefix_opt_lhs(&self.get_infinite()); - rem.is_identity() - .then(|| Self::new_unchecked(rel, target.dim_in(), target.len_in()).into()) - } -} - -impl RelativeTo for Concatenation -where - Item: BoundedMap + RelativeTo, - Target: BoundedMap, -{ - fn relative_to(&self, target: &Target) -> Option { - self.iter() - .map(|item| item.relative_to(target)) - .collect::>() - .map(|rel_items| Concatenation::new(rel_items).into()) - } -} - -// trait PartialMakeRelative { -// fn partial_make_relative(&self, target: Target) -> Option, Vec)>>; -// } - -impl RelativeTo> for WithBounds> { - fn relative_to(&self, targets: &Concatenation) -> Option { - let mut rels_indices = Vec::new(); - let mut offset = 0; - for target in targets.iter() { - let (common, rem, rel) = target.get_infinite().remove_common_prefix_opt_lhs(&self.get_infinite()); - let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); - if rem.is_identity() { - let rel: Vec = iter::once(slice).chain(rel).collect(); - let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); - return Some(ConcreteMap::Elementary(rel)); - } - if rem.dim_out() == 0 { - let mut indices: Vec = (0..target.len_in()).collect(); - rem.apply_indices_inplace(&mut indices); - rels_indices.push((rel, slice, indices)) - } - offset += target.len_in(); - } - // TODO: Split off common tail. - let mut concat_indices = Vec::new(); - let mut rels = Vec::new(); - for (irel, (rel, slice, out_indices)) in rels_indices.into_iter().enumerate() { - let in_indices = rel.unapply_indices(&out_indices); - let offset = irel * self.len_in(); - concat_indices.extend(in_indices.into_iter().map(|i| i + offset)); - let rel: Vec = iter::once(slice).chain(rel).collect(); - let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); - let rel = ConcreteMap::Elementary(rel); - rels.push(rel); - } - if concat_indices.len() != self.len_in() { - return None; - } - let take = Elementary::new_take(concat_indices, self.len_in() * rels.len()); - let take: ConcreteMap = WithBounds::new_unchecked(vec![take], self.dim_in(), self.len_in()).into(); - let concat = ConcreteMap::new_concatenation(rels); - Some(take.compose(concat).unwrap().into()) - } -} - -impl RelativeTo for WithBounds> { - fn relative_to(&self, target: &ConcreteMap) -> Option { - match target { - ConcreteMap::Elementary(target) => self.relative_to(target), - ConcreteMap::Concatenation(target) => { - let c: Option>>> = target.iter().map(|item| match item { - ConcreteMap::Elementary(item) => Some(item.clone()), - _ => None, - }).collect(); - c.and_then(|c| self.relative_to(&Concatenation::new(c))) - } - _ => None - } - } -} - -impl RelativeTo for ConcreteMap { - fn relative_to(&self, target: &Self) -> Option { - match self { - ConcreteMap::Elementary(source) => source.relative_to(target), - ConcreteMap::Concatenation(source) => source.relative_to(target), - _ => None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::infinite::*; - use crate::simplex::Simplex::*; - use approx::assert_abs_diff_eq; - use std::iter; - - #[test] - fn remove_common_prefix() { - let c1 = Elementary::new_children(Line); - let e1 = Elementary::new_edges(Line); - let swap_ec1 = Elementary::new_take([2, 1], 4); - let a = vec![c1.clone(), c1.clone()]; - let b = vec![e1.clone()]; - assert_eq!( - a.remove_common_prefix(&b), - ( - vec![c1.clone(), c1.clone()], - vec![], - vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], - ) - ); - } - - macro_rules! elem_comp { - (dim=$dim_out:literal, len=$len_out:literal) => { - WithBounds::>::new(Vec::new(), $dim_out, $len_out).unwrap() - }; - (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { - WithBounds::>::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() - }; - } - - macro_rules! assert_equiv_chains { - ($a:expr, $b:expr $(, $simplex:ident)*) => {{ - let a = $a; - let b = $b; - println!("a: {a:?}"); - println!("b: {b:?}"); - // Build coords: the outer product of the vertices of the given simplices, zero-padded - // to the dimension of the root. - let coords = iter::once([]); - let simplex_dim = 0; - $( - let coords = coords.flat_map(|coord| { - $simplex - .vertices() - .chunks($simplex.dim()) - .map(move |vert| [&coord, vert].concat()) - }); - let simplex_dim = simplex_dim + $simplex.dim(); - )* - assert_eq!(simplex_dim, a.dim_in(), "the given simplices don't add up to the input dimension"); - let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); - let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); - // Test if every input maps to the same output for both `a` and `b`. - for i in 0..2 * a.len_in() { - let mut crds_a = coords.clone(); - let mut crds_b = coords.clone(); - let ja = a.apply_inplace(i, &mut crds_a); - let jb = b.apply_inplace(i, &mut crds_b); - assert_eq!(ja, jb, "i={i}"); - assert_abs_diff_eq!(crds_a[..], crds_b[..]); - } - }}; - } - - #[test] - fn rel_to() { - let a1: ConcreteMap = elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([0, 2], 4)).into(); - let a2: ConcreteMap = elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([1, 3], 4), Children::new(Line)).into(); - let a = ConcreteMap::new_concatenation(vec![a1, a2].into()); - let b: ConcreteMap = elem_comp!(dim=1, len=2 <- Children::new(Line), Children::new(Line)).into(); - assert_equiv_chains!( - b.relative_to(&a).unwrap().compose(a.clone()).unwrap(), - b, - Line); - } -} +//trait RelativeTo { +// fn relative_to(&self, target: &Target) -> Option; +//} +// +//impl RelativeTo for WithBounds> { +// fn relative_to(&self, target: &Self) -> Option { +// let (common, rem, rel) = target +// .get_infinite() +// .remove_common_prefix_opt_lhs(&self.get_infinite()); +// rem.is_identity() +// .then(|| Self::new_unchecked(rel, target.dim_in(), target.len_in()).into()) +// } +//} +// +//impl RelativeTo for Concatenation +//where +// Item: BoundedMap + RelativeTo, +// Target: BoundedMap, +//{ +// fn relative_to(&self, target: &Target) -> Option { +// self.iter() +// .map(|item| item.relative_to(target)) +// .collect::>() +// .map(|rel_items| Concatenation::new(rel_items).into()) +// } +//} +// +//fn pop_common(vecs: &mut [&mut Vec]) -> Option { +// let item = vecs.first().and_then(|vec| vec.last()); +// if item.is_some() && vecs[1..].iter().all(|vec| vec.last() == item) { +// for vec in vecs[1..].iter_mut() { +// vec.pop(); +// } +// vecs[0].pop() +// } else { +// None +// } +//} +// +//#[derive(Debug, Clone)] +//struct IndexOutIn(usize, usize); +// +//impl UnapplyIndicesData for IndexOutIn { +// #[inline] +// fn last(&self) -> usize { +// self.1 +// } +// #[inline] +// fn push(&self, index: usize) -> Self { +// Self(self.0, index) +// } +//} +// +//impl RelativeTo> for WithBounds> { +// fn relative_to(&self, targets: &Concatenation) -> Option { +// let mut rels_indices = Vec::new(); +// let mut offset = 0; +// for target in targets.iter() { +// let (common, rem, rel) = target.get_infinite().remove_common_prefix_opt_lhs(&self.get_infinite()); +// if rem.is_identity() { +// let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); +// let rel: Vec = iter::once(slice).chain(rel).collect(); +// let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); +// return Some(Relative::Single(rel)); +// } +// if rem.dim_out() == 0 { +// let mut indices: Vec = (0..target.len_in()).collect(); +// rem.apply_indices_inplace(&mut indices); +// rels_indices.push((rel, offset, indices)) +// } +// offset += target.len_in(); +// } +// // Split off common tail. +// let mut common_len_out = self.len_in(); +// let mut common_dim_out = self.dim_in(); +// let mut common = Vec::new(); +// { +// let mut rels: Vec<_> = rels_indices.iter_mut().map(|(rel, _, _)| rel).collect(); +// while let Some(item) = pop_common(&mut rels[..]) { +// common_len_out = common_len_out / item.mod_in() * item.mod_out(); +// common_dim_out += item.delta_dim(); +// common.push(item); +// } +// } +// // Build index map. +// let mut index_map = iter::repeat(Some((0, 0))).take(self.len_in()).collect(); +// let mut rels = Vec::new(); +// for (irel, (rel, offset, out_indices)) in rels_indices.into_iter().enumerate() { +// let rel_indices: Vec<_> = (offset..offset+out_indices.len()).zip(out_indices).collect(); +// for IndexOutIn(iout, iin) in rel.unapply_indices(&rel_indices) { +// assert!(index_map[iin].is_none(), "target contains duplicate entries"); +// index_map[iin] = Some((iout, iin + irel * self.len_in())); +// } +// rels.push(rel); +// } +// let index_map = if let Some(index_map) = index_map.into_iter().collect() { +// index_map +// } else { +// return None; +// } +// Some(WithBounds { +// map: Relative::Concatenation { rels, index_map }, +// delta_dim: targets.dim_in() - self.dim_in(), +// dim_in: self.dim_in(), +// len_out: targets.len_out(), +// len_in: self.len_in(), +// }) +// } +//} +// +//#[derive(Debug, Clone, PartialEq)] +//pub enum Relative { +// Identity { dim: usize, len: usize }, +// Single(WithBounds>), +// Multiple { +// rels: Vec>, +// index_map: Vec<(usize, usize)>, +// len_out: usize, +// } +// Concatenation(Concatenation), +//} +// +//impl BoundedMap for Relative { +// fn len_out(&self) -> usize { +// match self { +// Self::Identity { len, .. } => len, +// Self::Single(map) => map.len_out(), +// Self::Multiple { len_out, .. } => len_out, +// Self::cConcn +// fn len_in(&self) -> usize; +// fn dim_out(&self) -> usize { +// self.dim_in() + self.delta_dim() +// } +// fn dim_in(&self) -> usize; +// fn delta_dim(&self) -> usize; +// fn add_offset(&mut self, offset: usize); +// fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize; +// fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { +// if index < self.len_in() { +// Some(self.apply_inplace_unchecked(index, coordinates)) +// } else { +// None +// } +// } +// fn apply_index_unchecked(&self, index: usize) -> usize; +// fn apply_index(&self, index: usize) -> Option { +// if index < self.len_in() { +// Some(self.apply_index_unchecked(index)) +// } else { +// None +// } +// } +// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { +// for index in indices.iter_mut() { +// *index = self.apply_index_unchecked(*index); +// } +// } +// fn apply_indices(&self, indices: &[usize]) -> Option> { +// if indices.iter().all(|index| *index < self.len_in()) { +// let mut indices = indices.to_vec(); +// self.apply_indices_inplace_unchecked(&mut indices); +// Some(indices) +// } else { +// None +// } +// } +// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec; +// fn unapply_indices(&self, indices: &[T]) -> Option> { +// if indices.iter().all(|index| index.last() < self.len_out()) { +// Some(self.unapply_indices_unchecked(indices)) +// } else { +// None +// } +// } +// fn is_identity(&self) -> bool; +// +// +//impl Relative { +// pub fn compose(self, other: Self) -> Result { +// Ok(Composition::new(self, other)?.into()) +// } +// pub fn new_concatenation(items: Vec) -> Self { +// // TODO: unravel nested concatenations? +// Self::Concatenation(Concatenation::new(items)) +// } +//} +// +//macro_rules! dispatch { +// ( +// $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( +// &$self:ident $(, $arg:ident: $ty:ty)* +// ) $($ret:tt)* +// ) => { +// #[inline] +// $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $($ret)* { +// dispatch!(@match $self; $fn; $($arg),*) +// } +// }; +// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { +// #[inline] +// $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { +// dispatch!(@match $self; $fn; $($arg),*) +// } +// }; +// (@match $self:ident; $fn:ident; $($arg:ident),*) => { +// match $self { +// Relative::Elementary(var) => var.$fn($($arg),*), +// Relative::Composition(var) => var.$fn($($arg),*), +// Relative::Concatenation(var) => var.$fn($($arg),*), +// } +// } +//} +// +//impl BoundedMap for Relative { +// dispatch!{fn len_out(&self) -> usize} +// dispatch!{fn len_in(&self) -> usize} +// dispatch!{fn dim_out(&self) -> usize} +// dispatch!{fn dim_in(&self) -> usize} +// dispatch!{fn delta_dim(&self) -> usize} +// dispatch!{fn add_offset(&mut self, offset: usize)} +// dispatch!{fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize} +// dispatch!{fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option} +// dispatch!{fn apply_index_unchecked(&self, index: usize) -> usize} +// dispatch!{fn apply_index(&self, index: usize) -> Option} +// dispatch!{fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} +// dispatch!{fn apply_indices(&self, indices: &[usize]) -> Option>} +// dispatch!{fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} +// dispatch!{fn unapply_indices(&self, indices: &[T]) -> Option>} +// dispatch!{fn is_identity(&self) -> bool} +//} +// +//impl From>> for Relative { +// fn from(map: WithBounds>) -> Self { +// Relative::Elementary(map) +// } +//} +// +//impl From> for Relative { +// fn from(map: Composition) -> Self { +// Relative::Composition(Box::new(map)) +// } +//} +// +//impl From> for Relative { +// fn from(map: Concatenation) -> Self { +// Relative::Concatenation(map) +// } +//} +// +//#[cfg(test)] +//mod tests { +// use super::*; +// use crate::infinite::*; +// use crate::simplex::Simplex::*; +// use approx::assert_abs_diff_eq; +// use std::iter; +// +// #[test] +// fn remove_common_prefix() { +// let c1 = Elementary::new_children(Line); +// let e1 = Elementary::new_edges(Line); +// let swap_ec1 = Elementary::new_take([2, 1], 4); +// let a = vec![c1.clone(), c1.clone()]; +// let b = vec![e1.clone()]; +// assert_eq!( +// a.remove_common_prefix(&b), +// ( +// vec![c1.clone(), c1.clone()], +// vec![], +// vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], +// ) +// ); +// } +// +// macro_rules! elem_comp { +// (dim=$dim_out:literal, len=$len_out:literal) => { +// WithBounds::>::new(Vec::new(), $dim_out, $len_out).unwrap() +// }; +// (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { +// WithBounds::>::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() +// }; +// } +// +// macro_rules! assert_equiv_chains { +// ($a:expr, $b:expr $(, $simplex:ident)*) => {{ +// let a = $a; +// let b = $b; +// println!("a: {a:?}"); +// println!("b: {b:?}"); +// // Build coords: the outer product of the vertices of the given simplices, zero-padded +// // to the dimension of the root. +// let coords = iter::once([]); +// let simplex_dim = 0; +// $( +// let coords = coords.flat_map(|coord| { +// $simplex +// .vertices() +// .chunks($simplex.dim()) +// .map(move |vert| [&coord, vert].concat()) +// }); +// let simplex_dim = simplex_dim + $simplex.dim(); +// )* +// assert_eq!(simplex_dim, a.dim_in(), "the given simplices don't add up to the input dimension"); +// let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); +// let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); +// // Test if every input maps to the same output for both `a` and `b`. +// for i in 0..2 * a.len_in() { +// let mut crds_a = coords.clone(); +// let mut crds_b = coords.clone(); +// let ja = a.apply_inplace(i, &mut crds_a); +// let jb = b.apply_inplace(i, &mut crds_b); +// assert_eq!(ja, jb, "i={i}"); +// assert_abs_diff_eq!(crds_a[..], crds_b[..]); +// } +// }}; +// } +// +// #[test] +// fn rel_to() { +// let a1 = elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([0, 2], 4)); +// let a2 = elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([1, 3], 4), Children::new(Line)); +// let a = Concatenation::new(vec![a1, a2]); +// let b = elem_comp!(dim=1, len=2 <- Children::new(Line), Children::new(Line), Children::new(Line)); +// assert_equiv_chains!( +// Composition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), +// b, +// Line); +// panic!{} +// } +//} From 3549cebd57126f7964c4a71135b9c2bcc507bbdb Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 22 Jun 2022 14:03:54 +0200 Subject: [PATCH 17/45] WIP --- src/{infinite.rs => elementary.rs} | 174 +++----- src/finite.rs | 295 ------------- src/lib.rs | 89 +++- src/ops.rs | 383 +++++++++++++++++ src/relative_to.rs | 665 ++++++++++++++--------------- 5 files changed, 853 insertions(+), 753 deletions(-) rename src/{infinite.rs => elementary.rs} (86%) delete mode 100644 src/finite.rs create mode 100644 src/ops.rs diff --git a/src/infinite.rs b/src/elementary.rs similarity index 86% rename from src/infinite.rs rename to src/elementary.rs index 26cc207d5..4dbb97374 100644 --- a/src/infinite.rs +++ b/src/elementary.rs @@ -1,40 +1,9 @@ use crate::finite_f64::FiniteF64; -use crate::UnapplyIndicesData; use crate::simplex::Simplex; +use crate::{UnapplyIndicesData, UnboundedMap}; use num::Integer as _; -use std::ops::{Deref, DerefMut}; use std::rc::Rc; -pub trait UnboundedMap { - // Minimum dimension of the input coordinate. If the dimension of the input - // coordinate of [UnboundedMap::apply()] is larger than the minimum, then - // the map of the surplus is the identity map. - fn dim_in(&self) -> usize; - // Minimum dimension of the output coordinate. - fn dim_out(&self) -> usize { - self.dim_in() + self.delta_dim() - } - // Difference in dimension of the output and input coordinate. - fn delta_dim(&self) -> usize; - fn add_offset(&mut self, offset: usize); - // Modulus of the input index. The map repeats itself at index `mod_in` - // and the output index is incremented with `in_index / mod_in * mod_out`. - fn mod_in(&self) -> usize; - // Modulus if the output index. - fn mod_out(&self) -> usize; - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize; - fn apply_index(&self, index: usize) -> usize; - fn apply_indices_inplace(&self, indices: &mut [usize]) { - for index in indices.iter_mut() { - *index = self.apply_index(*index); - } - } - fn unapply_indices(&self, indices: &[T]) -> Vec; - fn is_identity(&self) -> bool { - self.mod_in() == 1 && self.mod_out() == 1 && self.dim_out() == 0 - } -} - #[inline] const fn divmod(x: usize, y: usize) -> (usize, usize) { (x / y, x % y) @@ -57,6 +26,37 @@ fn coordinates_iter_mut( }) } +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Identity; + +impl UnboundedMap for Identity { + fn dim_in(&self) -> usize { + 0 + } + fn delta_dim(&self) -> usize { + 0 + } + fn add_offset(&mut self, _offset: usize) {} + fn mod_in(&self) -> usize { + 1 + } + fn mod_out(&self) -> usize { + 1 + } + fn apply_inplace(&self, index: usize, _coordinates: &mut [f64], _stride: usize) -> usize { + index + } + fn apply_index(&self, index: usize) -> usize { + index + } + fn unapply_indices(&self, indices: &[T]) -> Vec { + indices.to_vec() + } + fn is_identity(&self) -> bool { + true + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Transpose(usize, usize); @@ -111,14 +111,14 @@ impl UnboundedMap for Transpose { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Take { - indices: Rc>, + indices: Rc<[usize]>, nindices: usize, len: usize, } impl Take { - pub fn new(indices: impl Into>, len: usize) -> Self { - let indices = Rc::new(indices.into()); + pub fn new(indices: impl Into>, len: usize) -> Self { + let indices = indices.into(); let nindices = indices.len(); Take { indices, @@ -306,15 +306,15 @@ impl From for Edges { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct UniformPoints { - points: Rc>, + points: Rc<[FiniteF64]>, npoints: usize, point_dim: usize, offset: usize, } impl UniformPoints { - pub fn new(points: impl Into>, point_dim: usize) -> Self { - let points: Rc> = Rc::new(unsafe { std::mem::transmute(points.into()) }); + pub fn new(points: impl Into>, point_dim: usize) -> Self { + let points: Rc<[FiniteF64]> = unsafe { std::mem::transmute(points.into()) }; assert_eq!(points.len() % point_dim, 0); assert_ne!(point_dim, 0); let npoints = points.len() / point_dim; @@ -378,7 +378,7 @@ impl Elementary { Self::Transpose(Transpose::new(len1, len2)) } #[inline] - pub fn new_take(indices: impl Into>, len: usize) -> Self { + pub fn new_take(indices: impl Into>, len: usize) -> Self { Self::Take(Take::new(indices, len)) } #[inline] @@ -394,7 +394,7 @@ impl Elementary { Self::Edges(Edges::new(simplex)) } #[inline] - pub fn new_uniform_points(points: impl Into>, point_dim: usize) -> Self { + pub fn new_uniform_points(points: impl Into>, point_dim: usize) -> Self { Self::UniformPoints(UniformPoints::new(points, point_dim)) } #[inline] @@ -415,6 +415,13 @@ impl Elementary { self.set_offset(new_offset); self } + //pub fn swap(&mut self, other: &Self) -> Option> { + // if self.mod_out() == 1 { + // + // } else { + // None + // } + //} pub fn shift_left(&self, items: &[Self]) -> Option<(Option, Self, Vec)> { if self.is_transpose() { return None; @@ -601,75 +608,6 @@ impl From for Elementary { } } -fn dim_out_in(items: &[M]) -> (usize, usize) { - let mut dim_in = 0; - let mut dim_out = 0; - for item in items.iter().rev() { - if let Some(n) = item.dim_in().checked_sub(dim_out) { - dim_in += n; - dim_out += n; - } - dim_out += item.delta_dim(); - } - (dim_out, dim_in) -} - -fn mod_out_in(items: &[M]) -> (usize, usize) { - let mut mod_out = 1; - let mut mod_in = 1; - for item in items.iter().rev() { - let n = mod_out.lcm(&item.mod_in()); - mod_in *= n / mod_out; - mod_out = n / item.mod_in() * item.mod_out(); - } - (mod_out, mod_in) -} - -/// Composition. -impl UnboundedMap for Array -where - Item: UnboundedMap, - Array: Deref + DerefMut, -{ - fn dim_in(&self) -> usize { - dim_out_in(self.deref()).1 - } - fn delta_dim(&self) -> usize { - self.iter().map(|item| item.delta_dim()).sum() - } - fn add_offset(&mut self, offset: usize) { - for item in self.iter_mut() { - item.add_offset(offset); - } - } - fn mod_in(&self) -> usize { - mod_out_in(self.deref()).1 - } - fn mod_out(&self) -> usize { - mod_out_in(self.deref()).0 - } - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { - self.iter().rev().fold(index, |index, item| { - item.apply_inplace(index, coordinates, stride) - }) - } - fn apply_index(&self, index: usize) -> usize { - self.iter() - .rev() - .fold(index, |index, item| item.apply_index(index)) - } - fn apply_indices_inplace(&self, indices: &mut [usize]) { - for item in self.iter().rev() { - item.apply_indices_inplace(indices); - } - } - fn unapply_indices(&self, indices: &[T]) -> Vec { - self.iter().fold(indices.to_vec(), |indices, item| { - item.unapply_indices(&indices) - }) - } -} - #[cfg(test)] mod tests { use super::*; @@ -727,7 +665,7 @@ mod tests { #[test] fn apply_take() { - let item = Elementary::new_take([4, 1, 2], 5); + let item = Elementary::new_take(vec![4, 1, 2], 5); assert_eq_apply!(item, 0, 4); assert_eq_apply!(item, 1, 1); assert_eq_apply!(item, 2, 2); @@ -766,7 +704,7 @@ mod tests { #[test] fn apply_uniform_points() { - let item = Elementary::new_uniform_points([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); + let item = Elementary::new_uniform_points(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); assert_eq_apply!(item, 0, [[]], 0, [[1.0, 2.0]]); assert_eq_apply!(item, 1, [[]], 0, [[3.0, 4.0]]); assert_eq_apply!(item, 2, [[]], 0, [[5.0, 6.0]]); @@ -802,7 +740,7 @@ mod tests { #[test] fn unapply_indices_take() { - assert_unapply!(Elementary::new_take([4, 1], 5)); + assert_unapply!(Elementary::new_take(vec![4, 1], 5)); } #[test] @@ -818,12 +756,12 @@ mod tests { #[test] fn unapply_indices_uniform_points() { assert_unapply!(Elementary::new_uniform_points( - [0.0, 0.0, 1.0, 0.0, 0.0, 1.0], + vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0], 2 )); } - macro_rules! assert_equiv_chains { + macro_rules! assert_equiv_maps { ($a:expr, $b:expr $(, $simplex:ident)*) => {{ let a: &[Elementary] = &$a; let b: &[Elementary] = &$b; @@ -892,20 +830,20 @@ mod tests { } shifted.push(litem); shifted.extend(lchain.into_iter()); - assert_equiv_chains!(&shifted[..], &unshifted[..] $(, $simplex)*); + assert_equiv_maps!(&shifted[..], &unshifted[..] $(, $simplex)*); }}; } #[test] fn shift_left() { assert_shift_left!( - Transpose::new(4, 3), Elementary::new_take([0, 1], 3); + Transpose::new(4, 3), Elementary::new_take(vec![0, 1], 3); ); assert_shift_left!( - Transpose::new(3, 5), Transpose::new(5, 4*3), Elementary::new_take([0, 1], 3); + Transpose::new(3, 5), Transpose::new(5, 4*3), Elementary::new_take(vec![0, 1], 3); ); assert_shift_left!( - Transpose::new(5, 4), Transpose::new(5*4, 3), Elementary::new_take([0, 1], 3); + Transpose::new(5, 4), Transpose::new(5*4, 3), Elementary::new_take(vec![0, 1], 3); ); assert_shift_left!( Elementary::new_children(Line).with_offset(1), Elementary::new_children(Line); diff --git a/src/finite.rs b/src/finite.rs deleted file mode 100644 index ea4537f83..000000000 --- a/src/finite.rs +++ /dev/null @@ -1,295 +0,0 @@ -use crate::infinite::{UnboundedMap, Elementary}; -use crate::UnapplyIndicesData; - -pub trait BoundedMap { - fn len_out(&self) -> usize; - fn len_in(&self) -> usize; - fn dim_out(&self) -> usize { - self.dim_in() + self.delta_dim() - } - fn dim_in(&self) -> usize; - fn delta_dim(&self) -> usize; - fn add_offset(&mut self, offset: usize); - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize; - fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { - if index < self.len_in() { - Some(self.apply_inplace_unchecked(index, coordinates)) - } else { - None - } - } - fn apply_index_unchecked(&self, index: usize) -> usize; - fn apply_index(&self, index: usize) -> Option { - if index < self.len_in() { - Some(self.apply_index_unchecked(index)) - } else { - None - } - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - for index in indices.iter_mut() { - *index = self.apply_index_unchecked(*index); - } - } - fn apply_indices(&self, indices: &[usize]) -> Option> { - if indices.iter().all(|index| *index < self.len_in()) { - let mut indices = indices.to_vec(); - self.apply_indices_inplace_unchecked(&mut indices); - Some(indices) - } else { - None - } - } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec; - fn unapply_indices(&self, indices: &[T]) -> Option> { - if indices.iter().all(|index| index.last() < self.len_out()) { - Some(self.unapply_indices_unchecked(indices)) - } else { - None - } - } - fn is_identity(&self) -> bool; -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct ComposeCodomainDomainMismatch; - -impl std::error::Error for ComposeCodomainDomainMismatch {} - -impl std::fmt::Display for ComposeCodomainDomainMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "The codomain of the first map doesn't match the domain of the second map," - ) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Composition(M1, M2); - -impl Composition { - pub fn new(map1: M1, map2: M2) -> Result { - if map1.len_out() == map2.len_in() && map1.dim_out() == map2.dim_in() { - Ok(Self(map1, map2)) - } else { - Err(ComposeCodomainDomainMismatch) - } - } -} - -impl BoundedMap for Composition { - fn len_in(&self) -> usize { - self.0.len_in() - } - fn len_out(&self) -> usize { - self.1.len_out() - } - fn dim_in(&self) -> usize { - self.0.dim_in() - } - fn delta_dim(&self) -> usize { - self.0.delta_dim() + self.1.delta_dim() - } - fn add_offset(&mut self, offset: usize) { - self.0.add_offset(offset); - self.1.add_offset(offset); - } - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - let index = self.0.apply_inplace_unchecked(index, coordinates); - self.1.apply_inplace_unchecked(index, coordinates) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - let index = self.0.apply_index_unchecked(index); - self.1.apply_index_unchecked(index) - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.0.apply_indices_inplace_unchecked(indices); - self.1.apply_indices_inplace_unchecked(indices); - } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - let indices = self.1.unapply_indices_unchecked(indices); - self.0.unapply_indices_unchecked(&indices) - } - fn is_identity(&self) -> bool { - self.0.is_identity() && self.1.is_identity() - } -} - -pub trait Compose: BoundedMap + Sized { - fn compose( - self, - rhs: Rhs, - ) -> Result, ComposeCodomainDomainMismatch> { - Composition::new(self, rhs) - } -} - -impl Compose for M {} - -#[derive(Debug, Clone, PartialEq)] -pub struct Concatenation { - dim_in: usize, - delta_dim: usize, - len_out: usize, - len_in: usize, - items: Vec, -} - -impl Concatenation { - pub fn new(items: Vec) -> Self { - // TODO: Return `Result`. - let first = items.first().unwrap(); - let dim_in = first.dim_in(); - let delta_dim = first.delta_dim(); - let len_out = first.len_out(); - let mut len_in = 0; - for item in items.iter() { - assert_eq!(item.dim_in(), dim_in); - assert_eq!(item.delta_dim(), delta_dim); - assert_eq!(item.len_out(), len_out); - len_in += item.len_in(); - } - Self { - dim_in, - delta_dim, - len_out, - len_in, - items, - } - } - fn resolve_item_unchecked(&self, mut index: usize) -> (&Item, usize) { - for item in self.items.iter() { - if index < item.len_in() { - return (item, index); - } - index -= item.len_in(); - } - panic!("index out of range"); - } - pub fn iter(&self) -> impl Iterator { - self.items.iter() - } -} - -impl BoundedMap for Concatenation { - fn dim_in(&self) -> usize { - self.dim_in - } - fn delta_dim(&self) -> usize { - self.delta_dim - } - fn len_out(&self) -> usize { - self.len_out - } - fn len_in(&self) -> usize { - self.len_in - } - fn add_offset(&mut self, offset: usize) { - for item in self.items.iter_mut() { - item.add_offset(offset); - } - self.dim_in += offset; - } - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - let (item, index) = self.resolve_item_unchecked(index); - item.apply_inplace_unchecked(index, coordinates) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - let (item, index) = self.resolve_item_unchecked(index); - item.apply_index_unchecked(index) - } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - unimplemented! {} - } - fn is_identity(&self) -> bool { - false - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum NewWithBoundsError { - DimensionTooSmall, - LengthNotAMultipleOfRepetition, -} - -impl std::error::Error for NewWithBoundsError {} - -impl std::fmt::Display for NewWithBoundsError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::DimensionTooSmall => write!(f, "The dimension of the sized map is smaller than the minimum dimension of the unsized map."), - Self::LengthNotAMultipleOfRepetition => write!(f, "The length of the sized map is not a multiple of the repetition length of the unsized map."), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct WithBounds { - map: M, - delta_dim: usize, - dim_in: usize, - len_out: usize, - len_in: usize, -} - -impl WithBounds { - pub fn new(map: M, dim_out: usize, len_out: usize) -> Result { - if dim_out < map.dim_out() { - Err(NewWithBoundsError::DimensionTooSmall) - } else if len_out % map.mod_out() != 0 { - Err(NewWithBoundsError::LengthNotAMultipleOfRepetition) - } else { - Ok(Self::new_unchecked(map, dim_out, len_out)) - } - } - pub(crate) fn new_unchecked(map: M, dim_out: usize, len_out: usize) -> Self { - let delta_dim = map.delta_dim(); - let dim_in = dim_out - delta_dim; - let len_in = len_out / map.mod_out() * map.mod_in(); - Self { - map, - delta_dim, - dim_in, - len_out, - len_in, - } - } - pub fn get_infinite(&self) -> &M { - &self.map - } -} - -impl BoundedMap for WithBounds { - fn len_in(&self) -> usize { - self.len_in - } - fn len_out(&self) -> usize { - self.len_out - } - fn dim_in(&self) -> usize { - self.dim_in - } - fn delta_dim(&self) -> usize { - self.delta_dim - } - fn add_offset(&mut self, offset: usize) { - self.map.add_offset(offset); - self.dim_in += offset; - } - fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize { - self.map.apply_inplace(index, coordinates, self.dim_out()) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - self.map.apply_index(index) - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.map.apply_indices_inplace(indices) - } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - self.map.unapply_indices(indices) - } - fn is_identity(&self) -> bool { - self.map.is_identity() - } -} diff --git a/src/lib.rs b/src/lib.rs index 9be12708f..6a53b2e36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,94 @@ -pub mod finite; +pub mod elementary; pub mod finite_f64; -pub mod infinite; +pub mod ops; pub mod relative_to; pub mod simplex; +pub trait BoundedMap { + fn len_out(&self) -> usize; + fn len_in(&self) -> usize; + fn dim_out(&self) -> usize { + self.dim_in() + self.delta_dim() + } + fn dim_in(&self) -> usize; + fn delta_dim(&self) -> usize; + fn add_offset(&mut self, offset: usize); + fn apply_inplace_unchecked( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + ) -> usize; + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> Option { + if index < self.len_in() { + Some(self.apply_inplace_unchecked(index, coordinates, stride)) + } else { + None + } + } + fn apply_index_unchecked(&self, index: usize) -> usize; + fn apply_index(&self, index: usize) -> Option { + if index < self.len_in() { + Some(self.apply_index_unchecked(index)) + } else { + None + } + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + for index in indices.iter_mut() { + *index = self.apply_index_unchecked(*index); + } + } + fn apply_indices(&self, indices: &[usize]) -> Option> { + if indices.iter().all(|index| *index < self.len_in()) { + let mut indices = indices.to_vec(); + self.apply_indices_inplace_unchecked(&mut indices); + Some(indices) + } else { + None + } + } + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec; + fn unapply_indices(&self, indices: &[T]) -> Option> { + if indices.iter().all(|index| index.last() < self.len_out()) { + Some(self.unapply_indices_unchecked(indices)) + } else { + None + } + } + fn is_identity(&self) -> bool; +} + +pub trait UnboundedMap { + // Minimum dimension of the input coordinate. If the dimension of the input + // coordinate of [UnboundedMap::apply()] is larger than the minimum, then + // the map of the surplus is the identity map. + fn dim_in(&self) -> usize; + // Minimum dimension of the output coordinate. + fn dim_out(&self) -> usize { + self.dim_in() + self.delta_dim() + } + // Difference in dimension of the output and input coordinate. + fn delta_dim(&self) -> usize; + fn add_offset(&mut self, offset: usize); + // Modulus of the input index. The map repeats itself at index `mod_in` + // and the output index is incremented with `in_index / mod_in * mod_out`. + fn mod_in(&self) -> usize; + // Modulus if the output index. + fn mod_out(&self) -> usize; + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize; + fn apply_index(&self, index: usize) -> usize; + fn apply_indices_inplace(&self, indices: &mut [usize]) { + for index in indices.iter_mut() { + *index = self.apply_index(*index); + } + } + fn unapply_indices(&self, indices: &[T]) -> Vec; + fn is_identity(&self) -> bool { + self.mod_in() == 1 && self.mod_out() == 1 && self.dim_out() == 0 + } +} + pub trait UnapplyIndicesData: Clone { fn last(&self) -> usize; fn push(&self, index: usize) -> Self; diff --git a/src/ops.rs b/src/ops.rs new file mode 100644 index 000000000..9fd29b826 --- /dev/null +++ b/src/ops.rs @@ -0,0 +1,383 @@ +use crate::{BoundedMap, UnapplyIndicesData, UnboundedMap}; +use num::Integer as _; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum WithBoundsError { + DimensionTooSmall, + LengthNotAMultipleOfRepetition, +} + +impl std::error::Error for WithBoundsError {} + +impl std::fmt::Display for WithBoundsError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::DimensionTooSmall => write!(f, "The dimension of the sized map is smaller than the minimum dimension of the unsized map."), + Self::LengthNotAMultipleOfRepetition => write!(f, "The length of the sized map is not a multiple of the repetition length of the unsized map."), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct WithBounds { + map: M, + delta_dim: usize, + dim_in: usize, + len_out: usize, + len_in: usize, +} + +impl WithBounds { + pub fn new(map: M, dim_out: usize, len_out: usize) -> Result { + if dim_out < map.dim_out() { + Err(WithBoundsError::DimensionTooSmall) + } else if len_out % map.mod_out() != 0 { + Err(WithBoundsError::LengthNotAMultipleOfRepetition) + } else { + Ok(Self::new_unchecked(map, dim_out, len_out)) + } + } + pub(crate) fn new_unchecked(map: M, dim_out: usize, len_out: usize) -> Self { + let delta_dim = map.delta_dim(); + let dim_in = dim_out - delta_dim; + let len_in = len_out / map.mod_out() * map.mod_in(); + Self { + map, + delta_dim, + dim_in, + len_out, + len_in, + } + } + pub fn get_infinite(&self) -> &M { + &self.map + } +} + +impl BoundedMap for WithBounds { + fn len_in(&self) -> usize { + self.len_in + } + fn len_out(&self) -> usize { + self.len_out + } + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn add_offset(&mut self, offset: usize) { + self.map.add_offset(offset); + self.dim_in += offset; + } + fn apply_inplace_unchecked( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + ) -> usize { + self.map.apply_inplace(index, coordinates, stride) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + self.map.apply_index(index) + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.map.apply_indices_inplace(indices) + } + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + self.map.unapply_indices(indices) + } + fn is_identity(&self) -> bool { + self.map.is_identity() + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ComposeCodomainDomainMismatch; + +impl std::error::Error for ComposeCodomainDomainMismatch {} + +impl std::fmt::Display for ComposeCodomainDomainMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "The codomain of the first map doesn't match the domain of the second map," + ) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Composition { + inner: Inner, + outer: Outer, +} + +impl Composition { + pub fn new(inner: Inner, outer: Outer) -> Result { + if inner.len_out() == outer.len_in() && inner.dim_out() == outer.dim_in() { + Ok(Self { inner, outer }) + } else { + Err(ComposeCodomainDomainMismatch) + } + } +} + +impl BoundedMap for Composition { + fn len_in(&self) -> usize { + self.inner.len_in() + } + fn len_out(&self) -> usize { + self.outer.len_out() + } + fn dim_in(&self) -> usize { + self.inner.dim_in() + } + fn delta_dim(&self) -> usize { + self.inner.delta_dim() + self.outer.delta_dim() + } + fn add_offset(&mut self, offset: usize) { + self.inner.add_offset(offset); + self.outer.add_offset(offset); + } + fn apply_inplace_unchecked( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + ) -> usize { + let index = self + .inner + .apply_inplace_unchecked(index, coordinates, stride); + self.outer + .apply_inplace_unchecked(index, coordinates, stride) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + let index = self.inner.apply_index_unchecked(index); + self.outer.apply_index_unchecked(index) + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.inner.apply_indices_inplace_unchecked(indices); + self.outer.apply_indices_inplace_unchecked(indices); + } + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + let indices = self.outer.unapply_indices_unchecked(indices); + self.inner.unapply_indices_unchecked(&indices) + } + fn is_identity(&self) -> bool { + self.inner.is_identity() && self.outer.is_identity() + } +} + +impl UnboundedMap for Composition { + fn mod_in(&self) -> usize { + update_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).1 + } + fn mod_out(&self) -> usize { + update_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).0 + } + fn dim_in(&self) -> usize { + update_dim_out_in(&self.outer, self.inner.dim_out(), self.inner.dim_in()).0 + } + fn delta_dim(&self) -> usize { + self.inner.delta_dim() + self.outer.delta_dim() + } + fn add_offset(&mut self, offset: usize) { + self.inner.add_offset(offset); + self.outer.add_offset(offset); + } + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { + let index = self.inner.apply_inplace(index, coordinates, stride); + self.outer.apply_inplace(index, coordinates, stride) + } + fn apply_index(&self, index: usize) -> usize { + let index = self.inner.apply_index(index); + self.outer.apply_index(index) + } + fn apply_indices_inplace(&self, indices: &mut [usize]) { + self.inner.apply_indices_inplace(indices); + self.outer.apply_indices_inplace(indices); + } + fn unapply_indices(&self, indices: &[T]) -> Vec { + let indices = self.outer.unapply_indices(indices); + self.inner.unapply_indices(&indices) + } + fn is_identity(&self) -> bool { + self.inner.is_identity() && self.outer.is_identity() + } +} + +pub trait Compose: BoundedMap + Sized { + fn compose( + self, + rhs: Rhs, + ) -> Result, ComposeCodomainDomainMismatch> { + Composition::new(self, rhs) + } +} + +impl Compose for M {} + +#[derive(Debug, Clone, PartialEq)] +pub struct Concatenation { + dim_in: usize, + delta_dim: usize, + len_out: usize, + len_in: usize, + items: Vec, +} + +impl Concatenation { + pub fn new(items: Vec) -> Self { + // TODO: Return `Result`. + let first = items.first().unwrap(); + let dim_in = first.dim_in(); + let delta_dim = first.delta_dim(); + let len_out = first.len_out(); + let mut len_in = 0; + for item in items.iter() { + assert_eq!(item.dim_in(), dim_in); + assert_eq!(item.delta_dim(), delta_dim); + assert_eq!(item.len_out(), len_out); + len_in += item.len_in(); + } + Self { + dim_in, + delta_dim, + len_out, + len_in, + items, + } + } + fn resolve_item_unchecked(&self, mut index: usize) -> (&Item, usize) { + for item in self.items.iter() { + if index < item.len_in() { + return (item, index); + } + index -= item.len_in(); + } + panic!("index out of range"); + } + pub fn iter(&self) -> impl Iterator { + self.items.iter() + } +} + +impl BoundedMap for Concatenation { + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn len_out(&self) -> usize { + self.len_out + } + fn len_in(&self) -> usize { + self.len_in + } + fn add_offset(&mut self, offset: usize) { + for item in self.items.iter_mut() { + item.add_offset(offset); + } + self.dim_in += offset; + } + fn apply_inplace_unchecked( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + ) -> usize { + let (item, index) = self.resolve_item_unchecked(index); + item.apply_inplace_unchecked(index, coordinates, stride) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + let (item, index) = self.resolve_item_unchecked(index); + item.apply_index_unchecked(index) + } + fn unapply_indices_unchecked(&self, _indices: &[T]) -> Vec { + unimplemented! {} + } + fn is_identity(&self) -> bool { + false + } +} + +#[inline] +fn update_dim_out_in(map: &M, dim_out: usize, dim_in: usize) -> (usize, usize) { + if let Some(n) = map.dim_in().checked_sub(dim_out) { + (dim_out + n, dim_in + n) + } else { + (dim_out, dim_in) + } +} + +#[inline] +fn update_mod_out_in(map: &M, mod_out: usize, mod_in: usize) -> (usize, usize) { + let n = mod_out.lcm(&map.mod_in()); + (n / map.mod_in() * map.mod_out(), mod_in * n / mod_out) +} + +/// Composition. +impl UnboundedMap for Array +where + Item: UnboundedMap, + Array: Deref + DerefMut, +{ + fn dim_in(&self) -> usize { + self.deref() + .iter() + .rev() + .fold((0, 0), |(dim_out, dim_in), item| { + update_dim_out_in(item, dim_out, dim_in) + }) + .1 + } + fn delta_dim(&self) -> usize { + self.iter().map(|item| item.delta_dim()).sum() + } + fn add_offset(&mut self, offset: usize) { + for item in self.iter_mut() { + item.add_offset(offset); + } + } + fn mod_in(&self) -> usize { + self.deref() + .iter() + .rev() + .fold((1, 1), |(mod_out, mod_in), item| { + update_mod_out_in(item, mod_out, mod_in) + }) + .1 + } + fn mod_out(&self) -> usize { + self.deref() + .iter() + .rev() + .fold((1, 1), |(mod_out, mod_in), item| { + update_mod_out_in(item, mod_out, mod_in) + }) + .0 + } + fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { + self.iter().rev().fold(index, |index, item| { + item.apply_inplace(index, coordinates, stride) + }) + } + fn apply_index(&self, index: usize) -> usize { + self.iter() + .rev() + .fold(index, |index, item| item.apply_index(index)) + } + fn apply_indices_inplace(&self, indices: &mut [usize]) { + for item in self.iter().rev() { + item.apply_indices_inplace(indices); + } + } + fn unapply_indices(&self, indices: &[T]) -> Vec { + self.iter().fold(indices.to_vec(), |indices, item| { + item.unapply_indices(&indices) + }) + } +} diff --git a/src/relative_to.rs b/src/relative_to.rs index da231e83f..35f0e799a 100644 --- a/src/relative_to.rs +++ b/src/relative_to.rs @@ -1,9 +1,10 @@ -use crate::finite::{BoundedMap, WithBounds, Compose, Composition, Concatenation, ComposeCodomainDomainMismatch}; -use crate::infinite::{Edges, Elementary, UnboundedMap, Transpose}; +use crate::elementary::{Edges, Elementary, Identity, Transpose}; +use crate::ops::{Concatenation, WithBounds}; use crate::simplex::Simplex; -use crate::UnapplyIndicesData; +use crate::{BoundedMap, UnapplyIndicesData, UnboundedMap}; use std::collections::BTreeMap; use std::iter; +use std::rc::Rc; trait RemoveCommonPrefix: Sized { fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self); @@ -34,15 +35,19 @@ fn split_heads(items: &[Elementary]) -> BTreeMap, } impl RemoveCommonPrefix for Vec { - /// Remove and return the common prefix of two chains, transforming either if necessary. + /// Remove and return the common prefix of two maps, transforming either if necessary. fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { let mut common = Vec::new(); let mut tail1 = self.clone(); let mut tail2 = other.clone(); while !tail1.is_empty() && !tail2.is_empty() { - if tail1.last() == tail2.last() { - common.push(tail1.pop().unwrap()); - tail2.pop(); + if tail1.first() == tail2.first() { + let mut iter1 = tail1.drain(..); + let mut iter2 = tail2.drain(..); + common.push(iter1.next().unwrap()); + iter2.next(); + tail1 = iter1.collect(); + tail2 = iter2.collect(); continue; } let heads1: BTreeMap> = split_heads(&tail1) @@ -90,337 +95,321 @@ impl RemoveCommonPrefix for Vec { } tail1.reverse(); tail2.reverse(); - (common, tail1.into(), tail2.into()) + (common, tail1, tail2) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Relative { + Identity(WithBounds), + Single(WithBounds>), + Multiple(RelativeMultiple), + Concatenation(Concatenation), +} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $($ret:tt)* + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $($ret)* { + dispatch!(@match $self; $fn; $($arg),*) + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { + dispatch!(@match $self; $fn; $($arg),*) + } + }; + (@match $self:ident; $fn:ident; $($arg:ident),*) => { + match $self { + Relative::Identity(var) => var.$fn($($arg),*), + Relative::Single(var) => var.$fn($($arg),*), + Relative::Multiple(var) => var.$fn($($arg),*), + Relative::Concatenation(var) => var.$fn($($arg),*), + } + } +} + +impl BoundedMap for Relative { + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn add_offset(&mut self, offset: usize)} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> Option} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} +} + +trait RelativeTo { + fn relative_to(&self, target: &Target) -> Option; +} + +impl RelativeTo for WithBounds> { + fn relative_to(&self, target: &Self) -> Option { + let (_, rem, rel) = target + .get_infinite() + .remove_common_prefix_opt_lhs(self.get_infinite()); + rem.is_identity() + .then(|| Relative::Single(Self::new_unchecked(rel, target.dim_in(), target.len_in()))) + } +} + +impl RelativeTo for Concatenation +where + Item: BoundedMap + RelativeTo, + Target: BoundedMap, +{ + fn relative_to(&self, target: &Target) -> Option { + self.iter() + .map(|item| item.relative_to(target)) + .collect::>() + .map(|rel_items| Relative::Concatenation(Concatenation::new(rel_items))) + } +} + +fn pop_common(vecs: &mut [&mut Vec]) -> Option { + let item = vecs.first().and_then(|vec| vec.last()); + if item.is_some() && vecs[1..].iter().all(|vec| vec.last() == item) { + for vec in vecs[1..].iter_mut() { + vec.pop(); + } + vecs[0].pop() + } else { + None + } +} + +#[derive(Debug, Clone)] +struct IndexOutIn(usize, usize); + +impl UnapplyIndicesData for IndexOutIn { + #[inline] + fn last(&self) -> usize { + self.1 + } + #[inline] + fn push(&self, index: usize) -> Self { + Self(self.0, index) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct RelativeMultiple { + rels: Vec>, + index_map: Rc>, + common: Vec, + len_out: usize, + len_in: usize, + dim_in: usize, + delta_dim: usize, +} + +impl BoundedMap for RelativeMultiple { + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn len_in(&self) -> usize { + self.len_in + } + fn len_out(&self) -> usize { + self.len_out + } + fn add_offset(&mut self, offset: usize) { + self.common.add_offset(offset); + for rel in self.rels.iter_mut() { + rel.add_offset(offset); + } + self.dim_in += offset; + } + fn apply_inplace_unchecked( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + ) -> usize { + let index = self.common.apply_inplace(index, coordinates, stride); + let (iout, iin) = self.index_map[index]; + let n = self.index_map.len(); + self.rels[iin / n].apply_inplace(iin % n, coordinates, stride); + iout + } + fn apply_index_unchecked(&self, index: usize) -> usize { + self.index_map[self.common.apply_index(index)].0 + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.common.apply_indices_inplace(indices); + for index in indices.iter_mut() { + *index = self.index_map[*index].0; + } + } + fn unapply_indices_unchecked(&self, _indices: &[T]) -> Vec { + unimplemented! {} + } + fn is_identity(&self) -> bool { + false } } -//trait RelativeTo { -// fn relative_to(&self, target: &Target) -> Option; -//} -// -//impl RelativeTo for WithBounds> { -// fn relative_to(&self, target: &Self) -> Option { -// let (common, rem, rel) = target -// .get_infinite() -// .remove_common_prefix_opt_lhs(&self.get_infinite()); -// rem.is_identity() -// .then(|| Self::new_unchecked(rel, target.dim_in(), target.len_in()).into()) -// } -//} -// -//impl RelativeTo for Concatenation -//where -// Item: BoundedMap + RelativeTo, -// Target: BoundedMap, -//{ -// fn relative_to(&self, target: &Target) -> Option { -// self.iter() -// .map(|item| item.relative_to(target)) -// .collect::>() -// .map(|rel_items| Concatenation::new(rel_items).into()) -// } -//} -// -//fn pop_common(vecs: &mut [&mut Vec]) -> Option { -// let item = vecs.first().and_then(|vec| vec.last()); -// if item.is_some() && vecs[1..].iter().all(|vec| vec.last() == item) { -// for vec in vecs[1..].iter_mut() { -// vec.pop(); -// } -// vecs[0].pop() -// } else { -// None -// } -//} -// -//#[derive(Debug, Clone)] -//struct IndexOutIn(usize, usize); -// -//impl UnapplyIndicesData for IndexOutIn { -// #[inline] -// fn last(&self) -> usize { -// self.1 -// } -// #[inline] -// fn push(&self, index: usize) -> Self { -// Self(self.0, index) -// } -//} -// -//impl RelativeTo> for WithBounds> { -// fn relative_to(&self, targets: &Concatenation) -> Option { -// let mut rels_indices = Vec::new(); -// let mut offset = 0; -// for target in targets.iter() { -// let (common, rem, rel) = target.get_infinite().remove_common_prefix_opt_lhs(&self.get_infinite()); -// if rem.is_identity() { -// let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); -// let rel: Vec = iter::once(slice).chain(rel).collect(); -// let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); -// return Some(Relative::Single(rel)); -// } -// if rem.dim_out() == 0 { -// let mut indices: Vec = (0..target.len_in()).collect(); -// rem.apply_indices_inplace(&mut indices); -// rels_indices.push((rel, offset, indices)) -// } -// offset += target.len_in(); -// } -// // Split off common tail. -// let mut common_len_out = self.len_in(); -// let mut common_dim_out = self.dim_in(); -// let mut common = Vec::new(); -// { -// let mut rels: Vec<_> = rels_indices.iter_mut().map(|(rel, _, _)| rel).collect(); -// while let Some(item) = pop_common(&mut rels[..]) { -// common_len_out = common_len_out / item.mod_in() * item.mod_out(); -// common_dim_out += item.delta_dim(); -// common.push(item); -// } -// } -// // Build index map. -// let mut index_map = iter::repeat(Some((0, 0))).take(self.len_in()).collect(); -// let mut rels = Vec::new(); -// for (irel, (rel, offset, out_indices)) in rels_indices.into_iter().enumerate() { -// let rel_indices: Vec<_> = (offset..offset+out_indices.len()).zip(out_indices).collect(); -// for IndexOutIn(iout, iin) in rel.unapply_indices(&rel_indices) { -// assert!(index_map[iin].is_none(), "target contains duplicate entries"); -// index_map[iin] = Some((iout, iin + irel * self.len_in())); -// } -// rels.push(rel); -// } -// let index_map = if let Some(index_map) = index_map.into_iter().collect() { -// index_map -// } else { -// return None; -// } -// Some(WithBounds { -// map: Relative::Concatenation { rels, index_map }, -// delta_dim: targets.dim_in() - self.dim_in(), -// dim_in: self.dim_in(), -// len_out: targets.len_out(), -// len_in: self.len_in(), -// }) -// } -//} -// -//#[derive(Debug, Clone, PartialEq)] -//pub enum Relative { -// Identity { dim: usize, len: usize }, -// Single(WithBounds>), -// Multiple { -// rels: Vec>, -// index_map: Vec<(usize, usize)>, -// len_out: usize, -// } -// Concatenation(Concatenation), -//} -// -//impl BoundedMap for Relative { -// fn len_out(&self) -> usize { -// match self { -// Self::Identity { len, .. } => len, -// Self::Single(map) => map.len_out(), -// Self::Multiple { len_out, .. } => len_out, -// Self::cConcn -// fn len_in(&self) -> usize; -// fn dim_out(&self) -> usize { -// self.dim_in() + self.delta_dim() -// } -// fn dim_in(&self) -> usize; -// fn delta_dim(&self) -> usize; -// fn add_offset(&mut self, offset: usize); -// fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize; -// fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option { -// if index < self.len_in() { -// Some(self.apply_inplace_unchecked(index, coordinates)) -// } else { -// None -// } -// } -// fn apply_index_unchecked(&self, index: usize) -> usize; -// fn apply_index(&self, index: usize) -> Option { -// if index < self.len_in() { -// Some(self.apply_index_unchecked(index)) -// } else { -// None -// } -// } -// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { -// for index in indices.iter_mut() { -// *index = self.apply_index_unchecked(*index); -// } -// } -// fn apply_indices(&self, indices: &[usize]) -> Option> { -// if indices.iter().all(|index| *index < self.len_in()) { -// let mut indices = indices.to_vec(); -// self.apply_indices_inplace_unchecked(&mut indices); -// Some(indices) -// } else { -// None -// } -// } -// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec; -// fn unapply_indices(&self, indices: &[T]) -> Option> { -// if indices.iter().all(|index| index.last() < self.len_out()) { -// Some(self.unapply_indices_unchecked(indices)) -// } else { -// None -// } -// } -// fn is_identity(&self) -> bool; -// -// -//impl Relative { -// pub fn compose(self, other: Self) -> Result { -// Ok(Composition::new(self, other)?.into()) -// } -// pub fn new_concatenation(items: Vec) -> Self { -// // TODO: unravel nested concatenations? -// Self::Concatenation(Concatenation::new(items)) -// } -//} -// -//macro_rules! dispatch { -// ( -// $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( -// &$self:ident $(, $arg:ident: $ty:ty)* -// ) $($ret:tt)* -// ) => { -// #[inline] -// $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $($ret)* { -// dispatch!(@match $self; $fn; $($arg),*) -// } -// }; -// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { -// #[inline] -// $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { -// dispatch!(@match $self; $fn; $($arg),*) -// } -// }; -// (@match $self:ident; $fn:ident; $($arg:ident),*) => { -// match $self { -// Relative::Elementary(var) => var.$fn($($arg),*), -// Relative::Composition(var) => var.$fn($($arg),*), -// Relative::Concatenation(var) => var.$fn($($arg),*), -// } -// } -//} -// -//impl BoundedMap for Relative { -// dispatch!{fn len_out(&self) -> usize} -// dispatch!{fn len_in(&self) -> usize} -// dispatch!{fn dim_out(&self) -> usize} -// dispatch!{fn dim_in(&self) -> usize} -// dispatch!{fn delta_dim(&self) -> usize} -// dispatch!{fn add_offset(&mut self, offset: usize)} -// dispatch!{fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64]) -> usize} -// dispatch!{fn apply_inplace(&self, index: usize, coordinates: &mut [f64]) -> Option} -// dispatch!{fn apply_index_unchecked(&self, index: usize) -> usize} -// dispatch!{fn apply_index(&self, index: usize) -> Option} -// dispatch!{fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} -// dispatch!{fn apply_indices(&self, indices: &[usize]) -> Option>} -// dispatch!{fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} -// dispatch!{fn unapply_indices(&self, indices: &[T]) -> Option>} -// dispatch!{fn is_identity(&self) -> bool} -//} -// -//impl From>> for Relative { -// fn from(map: WithBounds>) -> Self { -// Relative::Elementary(map) -// } -//} -// -//impl From> for Relative { -// fn from(map: Composition) -> Self { -// Relative::Composition(Box::new(map)) -// } -//} -// -//impl From> for Relative { -// fn from(map: Concatenation) -> Self { -// Relative::Concatenation(map) -// } -//} -// -//#[cfg(test)] -//mod tests { -// use super::*; -// use crate::infinite::*; -// use crate::simplex::Simplex::*; -// use approx::assert_abs_diff_eq; -// use std::iter; -// -// #[test] -// fn remove_common_prefix() { -// let c1 = Elementary::new_children(Line); -// let e1 = Elementary::new_edges(Line); -// let swap_ec1 = Elementary::new_take([2, 1], 4); -// let a = vec![c1.clone(), c1.clone()]; -// let b = vec![e1.clone()]; -// assert_eq!( -// a.remove_common_prefix(&b), -// ( -// vec![c1.clone(), c1.clone()], -// vec![], -// vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], -// ) -// ); -// } -// -// macro_rules! elem_comp { -// (dim=$dim_out:literal, len=$len_out:literal) => { -// WithBounds::>::new(Vec::new(), $dim_out, $len_out).unwrap() -// }; -// (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { -// WithBounds::>::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() -// }; -// } -// -// macro_rules! assert_equiv_chains { -// ($a:expr, $b:expr $(, $simplex:ident)*) => {{ -// let a = $a; -// let b = $b; -// println!("a: {a:?}"); -// println!("b: {b:?}"); -// // Build coords: the outer product of the vertices of the given simplices, zero-padded -// // to the dimension of the root. -// let coords = iter::once([]); -// let simplex_dim = 0; -// $( -// let coords = coords.flat_map(|coord| { -// $simplex -// .vertices() -// .chunks($simplex.dim()) -// .map(move |vert| [&coord, vert].concat()) -// }); -// let simplex_dim = simplex_dim + $simplex.dim(); -// )* -// assert_eq!(simplex_dim, a.dim_in(), "the given simplices don't add up to the input dimension"); -// let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); -// let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); -// // Test if every input maps to the same output for both `a` and `b`. -// for i in 0..2 * a.len_in() { -// let mut crds_a = coords.clone(); -// let mut crds_b = coords.clone(); -// let ja = a.apply_inplace(i, &mut crds_a); -// let jb = b.apply_inplace(i, &mut crds_b); -// assert_eq!(ja, jb, "i={i}"); -// assert_abs_diff_eq!(crds_a[..], crds_b[..]); -// } -// }}; -// } -// -// #[test] -// fn rel_to() { -// let a1 = elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([0, 2], 4)); -// let a2 = elem_comp!(dim=1, len=2 <- Children::new(Line), Take::new([1, 3], 4), Children::new(Line)); -// let a = Concatenation::new(vec![a1, a2]); -// let b = elem_comp!(dim=1, len=2 <- Children::new(Line), Children::new(Line), Children::new(Line)); -// assert_equiv_chains!( -// Composition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), -// b, -// Line); -// panic!{} -// } -//} +impl RelativeTo> for WithBounds> { + fn relative_to(&self, targets: &Concatenation) -> Option { + let mut rels_indices = Vec::new(); + let mut offset = 0; + for target in targets.iter() { + let (_, rem, rel) = target + .get_infinite() + .remove_common_prefix_opt_lhs(&self.get_infinite()); + if rem.is_identity() { + let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); + let rel: Vec = iter::once(slice).chain(rel).collect(); + let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); + return Some(Relative::Single(rel)); + } + if rem.dim_out() == 0 { + let mut indices: Vec = (0..target.len_in()).collect(); + rem.apply_indices_inplace(&mut indices); + rels_indices.push((rel, offset, indices)) + } + offset += target.len_in(); + } + // Split off common tail. + let mut common_len_out = self.len_in(); + let mut common = Vec::new(); + { + let mut rels: Vec<_> = rels_indices.iter_mut().map(|(rel, _, _)| rel).collect(); + while let Some(item) = pop_common(&mut rels[..]) { + common_len_out = common_len_out / item.mod_in() * item.mod_out(); + common.push(item); + } + } + // Build index map. + let mut index_map: Vec> = + iter::repeat(None).take(common_len_out).collect(); + let mut rels = Vec::new(); + for (irel, (rel, offset, out_indices)) in rels_indices.into_iter().enumerate() { + let rel_indices: Vec<_> = (offset..offset + out_indices.len()) + .zip(out_indices) + .map(|(i, j)| IndexOutIn(i, j)) + .collect(); + for IndexOutIn(iout, iin) in rel.unapply_indices(&rel_indices) { + assert!( + index_map[iin].is_none(), + "target contains duplicate entries" + ); + index_map[iin] = Some((iout, iin + irel * common_len_out)); + } + rels.push(rel); + } + if let Some(index_map) = index_map.into_iter().collect::>>() { + Some(Relative::Multiple(RelativeMultiple { + index_map: index_map.into(), + rels, + common, + delta_dim: targets.dim_in() - self.dim_in(), + dim_in: self.dim_in(), + len_out: targets.len_in(), + len_in: self.len_in(), + })) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::elementary::*; + use crate::ops::Composition; + use crate::simplex::Simplex::*; + use approx::assert_abs_diff_eq; + use std::iter; + + #[test] + fn remove_common_prefix() { + let c1 = Elementary::new_children(Line); + let e1 = Elementary::new_edges(Line); + let swap_ec1 = Elementary::new_take(vec![2, 1], 4); + let a = vec![c1.clone(), c1.clone()]; + let b = vec![e1.clone()]; + assert_eq!( + a.remove_common_prefix(&b), + ( + vec![c1.clone(), c1.clone()], + vec![], + vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], + ) + ); + } + + macro_rules! single { + (dim=$dim_out:literal, len=$len_out:literal) => { + WithBounds::>::new(Vec::new(), $dim_out, $len_out).unwrap() + }; + (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { + WithBounds::>::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() + }; + } + + macro_rules! assert_equiv_maps { + ($a:expr, $b:expr $(, $simplex:ident)*) => {{ + let a = $a; + let b = $b; + println!("a: {a:?}"); + println!("b: {b:?}"); + // Build coords: the outer product of the vertices of the given simplices, zero-padded + // to the dimension of the root. + let coords = iter::once([]); + let simplex_dim = 0; + $( + let coords = coords.flat_map(|coord| { + $simplex + .vertices() + .chunks($simplex.dim()) + .map(move |vert| [&coord, vert].concat()) + }); + let simplex_dim = simplex_dim + $simplex.dim(); + )* + assert_eq!(simplex_dim, a.dim_in(), "the given simplices don't add up to the input dimension"); + let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); + let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); + // Test if every input maps to the same output for both `a` and `b`. + for i in 0..2 * a.len_in() { + let mut crds_a = coords.clone(); + let mut crds_b = coords.clone(); + let ja = a.apply_inplace(i, &mut crds_a, a.dim_out()); + let jb = b.apply_inplace(i, &mut crds_b, a.dim_out()); + assert_eq!(ja, jb, "i={i}"); + assert_abs_diff_eq!(crds_a[..], crds_b[..]); + } + }}; + } + + #[test] + fn rel_to() { + let a1 = single!(dim=1, len=2 <- Children::new(Line), Take::new(vec![0, 2], 4)); + let a2 = single!(dim=1, len=2 <- Children::new(Line), Take::new(vec![1, 3], 4), Children::new(Line)); + let a = Concatenation::new(vec![a1, a2]); + let b = + single!(dim=1, len=2 <- Children::new(Line), Children::new(Line), Children::new(Line)); + assert_equiv_maps!( + Composition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), + b, + Line + ); + } +} From b54dc90c998fde2b24d4e0983ae610bf8f32c812 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Thu, 23 Jun 2022 16:28:12 +0200 Subject: [PATCH 18/45] WIP --- src/elementary.rs | 19 +- src/lib.rs | 3 +- src/ops.rs | 112 ++++++--- src/relative_to.rs | 56 +++-- src/simplex.rs | 14 ++ src/topology.rs | 565 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 720 insertions(+), 49 deletions(-) create mode 100644 src/topology.rs diff --git a/src/elementary.rs b/src/elementary.rs index 4dbb97374..a2be6a04e 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -126,6 +126,9 @@ impl Take { len, } } + pub fn get_indices(&self) -> Rc<[usize]> { + self.indices.clone() + } } impl UnboundedMap for Take { @@ -362,7 +365,7 @@ impl UnboundedMap for UniformPoints { } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum Elementary { Transpose(Transpose), Take(Take), @@ -572,6 +575,10 @@ impl UnboundedMap for Elementary { dispatch! {fn is_identity(&self) -> bool} } +impl std::fmt::Debug for Elementary { + dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result} +} + impl From for Elementary { fn from(transpose: Transpose) -> Self { Self::Transpose(transpose) @@ -608,6 +615,16 @@ impl From for Elementary { } } +pub trait PushElementary { + fn push_elementary(&mut self, map: &Elementary); +} + +impl PushElementary for Vec { + fn push_elementary(&mut self, map: &Elementary) { + self.push(map.clone()); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 6a53b2e36..a503f64f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod finite_f64; pub mod ops; pub mod relative_to; pub mod simplex; +pub mod topology; pub trait BoundedMap { fn len_out(&self) -> usize; @@ -89,7 +90,7 @@ pub trait UnboundedMap { } } -pub trait UnapplyIndicesData: Clone { +pub trait UnapplyIndicesData: Clone + std::fmt::Debug { fn last(&self) -> usize; fn push(&self, index: usize) -> Self; } diff --git a/src/ops.rs b/src/ops.rs index 9fd29b826..ea3de5135 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,3 +1,4 @@ +use crate::elementary::{Elementary, PushElementary}; use crate::{BoundedMap, UnapplyIndicesData, UnboundedMap}; use num::Integer as _; use std::ops::{Deref, DerefMut}; @@ -28,7 +29,7 @@ pub struct WithBounds { len_in: usize, } -impl WithBounds { +impl WithBounds { pub fn new(map: M, dim_out: usize, len_out: usize) -> Result { if dim_out < map.dim_out() { Err(WithBoundsError::DimensionTooSmall) @@ -50,9 +51,12 @@ impl WithBounds { len_in, } } - pub fn get_infinite(&self) -> &M { + pub fn get_unbounded(&self) -> &M { &self.map } + pub fn into_unbounded(self) -> M { + self.map + } } impl BoundedMap for WithBounds { @@ -94,6 +98,23 @@ impl BoundedMap for WithBounds { } } +impl PushElementary for WithBounds { + fn push_elementary(&mut self, item: &Elementary) { + // TODO: return an error if we push something that causes `dim_in` to drop below zero + // or with incompatible length. + assert!(self.dim_in >= item.delta_dim()); + self.delta_dim += item.delta_dim(); + self.dim_in -= item.delta_dim(); + if item.mod_out() > 0 { + assert_eq!(self.len_in % item.mod_out(), 0); + self.len_in = self.len_in / item.mod_out() * item.mod_in(); + } else { + self.len_in = 0; + } + self.map.push_elementary(item); + } +} + #[derive(Debug, Clone, Copy, PartialEq)] pub struct ComposeCodomainDomainMismatch; @@ -220,13 +241,7 @@ pub trait Compose: BoundedMap + Sized { impl Compose for M {} #[derive(Debug, Clone, PartialEq)] -pub struct Concatenation { - dim_in: usize, - delta_dim: usize, - len_out: usize, - len_in: usize, - items: Vec, -} +pub struct Concatenation(Vec); impl Concatenation { pub fn new(items: Vec) -> Self { @@ -235,23 +250,15 @@ impl Concatenation { let dim_in = first.dim_in(); let delta_dim = first.delta_dim(); let len_out = first.len_out(); - let mut len_in = 0; for item in items.iter() { assert_eq!(item.dim_in(), dim_in); assert_eq!(item.delta_dim(), delta_dim); assert_eq!(item.len_out(), len_out); - len_in += item.len_in(); - } - Self { - dim_in, - delta_dim, - len_out, - len_in, - items, } + Self(items) } fn resolve_item_unchecked(&self, mut index: usize) -> (&Item, usize) { - for item in self.items.iter() { + for item in self.0.iter() { if index < item.len_in() { return (item, index); } @@ -260,28 +267,30 @@ impl Concatenation { panic!("index out of range"); } pub fn iter(&self) -> impl Iterator { - self.items.iter() + self.0.iter() + } + pub fn into_iter(self) -> impl Iterator { + self.0.into_iter() } } impl BoundedMap for Concatenation { fn dim_in(&self) -> usize { - self.dim_in + self.0.first().unwrap().dim_in() } fn delta_dim(&self) -> usize { - self.delta_dim + self.0.first().unwrap().delta_dim() } fn len_out(&self) -> usize { - self.len_out + self.0.first().unwrap().len_out() } fn len_in(&self) -> usize { - self.len_in + self.iter().map(|item| item.len_in()).sum() } fn add_offset(&mut self, offset: usize) { - for item in self.items.iter_mut() { + for item in &mut self.0 { item.add_offset(offset); } - self.dim_in += offset; } fn apply_inplace_unchecked( &self, @@ -296,18 +305,61 @@ impl BoundedMap for Concatenation { let (item, index) = self.resolve_item_unchecked(index); item.apply_index_unchecked(index) } - fn unapply_indices_unchecked(&self, _indices: &[T]) -> Vec { - unimplemented! {} + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + let mut result = Vec::new(); + let mut offset = 0; + for item in &self.0 { + result.extend( + item.unapply_indices_unchecked(indices) + .into_iter() + .map(|i| i.push(i.last() + offset)), + ); + offset += item.len_in(); + } + result } fn is_identity(&self) -> bool { false } } +impl PushElementary for Concatenation +where + M: BoundedMap + PushElementary, +{ + fn push_elementary(&mut self, map: &Elementary) { + match map { + Elementary::Children(_) | Elementary::Edges(_) => { + for item in &mut self.0 { + item.push_elementary(map); + } + } + Elementary::Take(take) => { + let mut offset = 0; + let indices = take.get_indices(); + let mut indices = indices.iter().cloned().peekable(); + for item in &mut self.0 { + let len = item.len_in(); + let mut item_indices = Vec::new(); + while let Some(index) = indices.next_if(|&i| i < offset + len) { + if index < offset { + unimplemented! {"take of concatenation with unordered indices"}; + } + item_indices.push(index - offset); + } + item.push_elementary(&Elementary::new_take(item_indices, len)); + offset += len; + } + } + _ => unimplemented! {}, + } + } +} + #[inline] fn update_dim_out_in(map: &M, dim_out: usize, dim_in: usize) -> (usize, usize) { if let Some(n) = map.dim_in().checked_sub(dim_out) { - (dim_out + n, dim_in + n) + (map.dim_out(), dim_in + n) } else { (dim_out, dim_in) } @@ -323,7 +375,7 @@ fn update_mod_out_in(map: &M, mod_out: usize, mod_in: usize) -> impl UnboundedMap for Array where Item: UnboundedMap, - Array: Deref + DerefMut, + Array: Deref + DerefMut + std::fmt::Debug, { fn dim_in(&self) -> usize { self.deref() diff --git a/src/relative_to.rs b/src/relative_to.rs index 35f0e799a..921989eb2 100644 --- a/src/relative_to.rs +++ b/src/relative_to.rs @@ -152,15 +152,23 @@ impl BoundedMap for Relative { dispatch! {fn is_identity(&self) -> bool} } -trait RelativeTo { +pub trait RelativeTo { fn relative_to(&self, target: &Target) -> Option; + fn unapply_indices_from( + &self, + target: &Target, + indices: &[T], + ) -> Option> { + self.relative_to(target) + .and_then(|rel| rel.unapply_indices(indices)) + } } impl RelativeTo for WithBounds> { fn relative_to(&self, target: &Self) -> Option { let (_, rem, rel) = target - .get_infinite() - .remove_common_prefix_opt_lhs(self.get_infinite()); + .get_unbounded() + .remove_common_prefix_opt_lhs(self.get_unbounded()); rem.is_identity() .then(|| Relative::Single(Self::new_unchecked(rel, target.dim_in(), target.len_in()))) } @@ -257,8 +265,20 @@ impl BoundedMap for RelativeMultiple { *index = self.index_map[*index].0; } } - fn unapply_indices_unchecked(&self, _indices: &[T]) -> Vec { - unimplemented! {} + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + // FIXME: VERY EXPENSIVE!!! + let mut in_indices: Vec = Vec::new(); + for index in indices { + in_indices.extend( + self.index_map + .iter() + .enumerate() + .filter_map(|(iin, (iout, _))| { + (*iout == index.last()).then(|| index.push(iin)) + }), + ); + } + self.common.unapply_indices(&in_indices) } fn is_identity(&self) -> bool { false @@ -271,8 +291,8 @@ impl RelativeTo> for WithBounds> { let mut offset = 0; for target in targets.iter() { let (_, rem, rel) = target - .get_infinite() - .remove_common_prefix_opt_lhs(&self.get_infinite()); + .get_unbounded() + .remove_common_prefix_opt_lhs(&self.get_unbounded()); if rem.is_identity() { let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); let rel: Vec = iter::once(slice).chain(rel).collect(); @@ -286,16 +306,18 @@ impl RelativeTo> for WithBounds> { } offset += target.len_in(); } - // Split off common tail. - let mut common_len_out = self.len_in(); - let mut common = Vec::new(); - { - let mut rels: Vec<_> = rels_indices.iter_mut().map(|(rel, _, _)| rel).collect(); - while let Some(item) = pop_common(&mut rels[..]) { - common_len_out = common_len_out / item.mod_in() * item.mod_out(); - common.push(item); - } - } + // Split off common tail. TODO: Only shape increasing items, not take, slice (and transpose?). + let common_len_out = self.len_in(); + let common = Vec::new(); + //let mut common_len_out = self.len_in(); + //let mut common = Vec::new(); + //{ + // let mut rels: Vec<_> = rels_indices.iter_mut().map(|(rel, _, _)| rel).collect(); + // while let Some(item) = pop_common(&mut rels[..]) { + // common_len_out = common_len_out / item.mod_in() * item.mod_out(); + // common.push(item); + // } + //} // Build index map. let mut index_map: Vec> = iter::repeat(None).take(common_len_out).collect(); diff --git a/src/simplex.rs b/src/simplex.rs index 813e54012..0b7d2c109 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -86,6 +86,20 @@ impl Simplex { } } + pub fn centroid(&self) -> Vec { + let scale = (self.dim() + 1) as f64; + (0..self.dim()) + .map(|j| { + self.vertices() + .iter() + .skip(j) + .step_by(self.dim()) + .sum::() + / scale + }) + .collect() + } + /// Transform the given child `coordinates` for child `index` to this parent /// simplex in-place. The returned index is the index of the parent in an /// infinite, uniform sequence. diff --git a/src/topology.rs b/src/topology.rs new file mode 100644 index 000000000..ab88a89b4 --- /dev/null +++ b/src/topology.rs @@ -0,0 +1,565 @@ +use crate::elementary::{Elementary, PushElementary}; +use crate::ops::{Concatenation, WithBounds}; +use crate::relative_to::RelativeTo; +use crate::simplex::Simplex; +use crate::BoundedMap; +use std::iter; +use std::rc::Rc; + +type Tesselation = Concatenation>>; + +#[derive(Debug, Clone, PartialEq)] +pub struct Root { + pub name: String, + pub dim: usize, +} + +impl Root { + pub fn new(name: String, dim: usize) -> Self { + Self { name, dim } + } +} + +pub trait TopologyCore: std::fmt::Debug + Clone + Into { + fn tesselation(&self) -> &Tesselation; + fn dim(&self) -> usize { + self.tesselation().dim_in() + } + fn ntiles(&self) -> usize { + self.tesselation().len_in() + } + fn refined(&self) -> Topology; + fn map_itiles_to_refined(&self, itiles: &[usize]) -> Vec { + self.refined() + .tesselation() + .unapply_indices_from(self.tesselation(), &itiles) + .unwrap() + } + fn boundary(&self) -> Topology; + fn take(&self, itiles: &[usize]) -> Topology { + let mut itiles: Vec = itiles.to_vec(); + itiles.sort_by_key(|&index| index); + Take::new(self.clone().into(), itiles.into()) + } + fn refined_by(&self, itiles: &[usize]) -> Topology { + Hierarchical::new(self.clone().into(), vec![(0..self.ntiles()).collect()]) + .refined_by(itiles) + } +} + +#[derive(Clone, PartialEq)] +pub enum Topology { + Point(Rc), + Line(Rc), + Product(Rc), + DisjointUnion(Rc), + Take(Rc), + Hierarchical(Rc), +} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $($ret:tt)* + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $($ret)* { + dispatch!(@match $self; $fn; $($arg),*) + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { + dispatch!(@match $self; $fn; $($arg),*) + } + }; + (@match $self:ident; $fn:ident; $($arg:ident),*) => { + match $self { + Self::Point(var) => var.$fn($($arg),*), + Self::Line(var) => var.$fn($($arg),*), + Self::Product(var) => var.$fn($($arg),*), + Self::DisjointUnion(var) => var.$fn($($arg),*), + Self::Take(var) => var.$fn($($arg),*), + Self::Hierarchical(var) => var.$fn($($arg),*), + } + } +} + +impl TopologyCore for Topology { + dispatch! {fn tesselation(&self) -> &Tesselation} + dispatch! {fn dim(&self) -> usize} + dispatch! {fn ntiles(&self) -> usize} + dispatch! {fn refined(&self) -> Topology} + dispatch! {fn map_itiles_to_refined(&self, itiles: &[usize]) -> Vec} + dispatch! {fn boundary(&self) -> Topology} + dispatch! {fn take(&self, itiles: &[usize]) -> Topology} + dispatch! {fn refined_by(&self, itiles: &[usize]) -> Topology} +} + +impl std::fmt::Debug for Topology { + dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result} +} + +macro_rules! impl_from_topo { + ($($from:tt),*) => { + $( + impl From<$from> for Topology { + fn from(topo: $from) -> Topology { + Topology::$from(Rc::new(topo)) + } + } + )* + }; +} + +impl_from_topo! {Point, Line, Product, DisjointUnion, Take, Hierarchical} + +#[derive(Debug, Clone, PartialEq)] +pub struct Point { + tesselation: Tesselation, +} + +impl TopologyCore for Point { + fn dim(&self) -> usize { + 0 + } + fn tesselation(&self) -> &Tesselation { + &self.tesselation + } + fn refined(&self) -> Topology { + self.clone().into() + } + fn boundary(&self) -> Topology { + panic!("the boundary of a Point does not exist"); + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Line { + tesselation: Tesselation, +} + +impl Line { + pub fn new(tesselation: Tesselation) -> Topology { + assert_eq!(tesselation.dim_in(), 1); + Self { tesselation }.into() + } + pub fn from_len(len: usize) -> Topology { + Self::new(Concatenation::new(vec![ + WithBounds::new(vec![], 1, len).unwrap() + ])) + } +} + +impl TopologyCore for Line { + fn dim(&self) -> usize { + 1 + } + fn tesselation(&self) -> &Tesselation { + &self.tesselation + } + fn refined(&self) -> Topology { + let mut tesselation = self.tesselation.clone(); + tesselation.push_elementary(&Elementary::new_children(Simplex::Line)); + Self { tesselation }.into() + } + fn boundary(&self) -> Topology { + let mut tesselation = self.tesselation.clone(); + tesselation.push_elementary(&Elementary::new_edges(Simplex::Line)); + let n = tesselation.len_in(); + tesselation.push_elementary(&Elementary::new_take(vec![1, n - 2], n)); + Point { tesselation }.into() + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct DisjointUnion { + topos: [Topology; 2], + tesselation: Tesselation, +} + +impl DisjointUnion { + pub fn new(topo0: Topology, topo1: Topology) -> Topology { + let tesselation = Concatenation::new( + topo0 + .tesselation() + .iter() + .chain(topo1.tesselation().iter()) + .cloned() + .collect(), + ); + Self { + topos: [topo0, topo1], + tesselation, + } + .into() + } +} + +impl TopologyCore for DisjointUnion { + fn tesselation(&self) -> &Tesselation { + &self.tesselation + } + fn refined(&self) -> Topology { + Self::new(self.topos[0].refined(), self.topos[1].refined()) + } + fn boundary(&self) -> Topology { + Self::new(self.topos[0].boundary(), self.topos[1].boundary()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Product { + topos: [Topology; 2], + tesselation: Tesselation, +} + +impl Product { + pub fn new(topo0: Topology, topo1: Topology) -> Topology { + let map0 = topo0.tesselation(); + let map1 = topo1.tesselation(); + let dim_out = map0.dim_out() + map1.dim_out(); + let len_out = map0.len_out() * map1.len_out(); + let mut parts = Vec::new(); + for part0 in topo0.tesselation().iter() { + for part1 in topo1.tesselation().iter() { + let part0 = part0.clone(); + let mut part1 = part1.clone(); + let trans1 = Elementary::new_transpose(part1.len_out(), part0.len_out()); + let trans2 = Elementary::new_transpose(part0.len_in(), part1.len_out()); + part1.add_offset(part0.dim_in()); + let part: Vec = iter::once(trans1) + .chain(part0.into_unbounded()) + .chain(iter::once(trans2)) + .chain(part1.into_unbounded()) + .collect(); + parts.push(WithBounds::new(part, dim_out, len_out).unwrap()); + } + } + let tesselation = Concatenation::new(parts); + // TODO: assert no common roots + Self { + topos: [topo0, topo1], + tesselation, + } + .into() + } +} + +impl TopologyCore for Product { + fn tesselation(&self) -> &Tesselation { + &self.tesselation + } + fn refined(&self) -> Topology { + Self::new(self.topos[0].refined(), self.topos[1].refined()) + } + fn boundary(&self) -> Topology { + DisjointUnion::new( + Product::new(self.topos[0].clone(), self.topos[1].boundary()), + Product::new(self.topos[0].boundary(), self.topos[1].clone()), + ) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Take { + topo: Topology, + itiles: Vec, + tesselation: Tesselation, +} + +impl Take { + pub fn new(topo: Topology, itiles: Vec) -> Topology { + // TODO: requires sorted itiles? + let mut tesselation = topo.tesselation().clone(); + tesselation.push_elementary(&Elementary::new_take(itiles.clone(), tesselation.len_in())); + Self { + topo, + itiles, + tesselation, + } + .into() + } +} + +impl TopologyCore for Take { + fn tesselation(&self) -> &Tesselation { + &self.tesselation + } + fn refined(&self) -> Topology { + let refined = self.topo.refined(); + let mut itiles = refined + .tesselation() + .unapply_indices_from(self.topo.tesselation(), &self.itiles) + .unwrap(); + itiles.sort_by_key(|&index| index); + Take::new(refined, itiles.into()) + } + fn boundary(&self) -> Topology { + unimplemented! {} + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Hierarchical { + base: Topology, + itiles: Vec>, + tesselation: Tesselation, +} + +fn refine_iter(base: Topology) -> impl Iterator { + iter::successors(Some(base), |topo| Some(topo.refined())) +} + +impl Hierarchical { + pub fn new(base: Topology, itiles: Vec>) -> Topology { + //println!("{itiles:?}"); + let tesselation = Concatenation::new( + itiles + .iter() + .zip(refine_iter(base.clone())) + .filter(|(itiles, _)| !itiles.is_empty()) + .flat_map(|(itiles, level)| { + //println!("{level:?} {itiles:?}"); + let mut tesselation = level.tesselation().clone(); + tesselation.push_elementary(&Elementary::new_take( + itiles.clone(), + tesselation.len_in(), + )); + tesselation.into_iter() + }) + .collect(), + ); + Self { + base, + itiles, + tesselation, + } + .into() + } + fn levels(&self) -> impl Iterator { + refine_iter(self.base.clone()) + } + fn itiles_levels(&self) -> impl Iterator, Topology)> { + self.itiles.iter().zip(self.levels()) + } +} + +impl TopologyCore for Hierarchical { + fn tesselation(&self) -> &Tesselation { + &self.tesselation + } + fn refined(&self) -> Topology { + let itiles = self + .itiles_levels() + .map(|(itiles, level)| level.map_itiles_to_refined(itiles)) + .collect(); + Hierarchical::new(self.base.refined(), itiles) + } + fn refined_by(&self, itiles: &[usize]) -> Topology { + let mut global_itiles = itiles.to_vec(); + global_itiles.sort_by_key(|&index| index); + let mut global_itiles = global_itiles.into_iter().peekable(); + let mut offset = 0; + let mut queue = Vec::new(); + let mut refined_itiles = Vec::new(); + for (level_itiles, level) in self.itiles_levels() { + let mut refined_level_itiles: Vec = queue.drain(..).collect(); + for (i, itile) in level_itiles.iter().cloned().enumerate() { + if global_itiles.next_if(|&j| i + offset == j).is_some() { + queue.push(itile); + } else { + refined_level_itiles.push(itile); + } + } + refined_level_itiles.sort_by_key(|&index| index); + refined_itiles.push(refined_level_itiles); + queue = level.map_itiles_to_refined(&queue); + offset += level_itiles.len(); + } + if !queue.is_empty() { + refined_itiles.push(queue); + } + Hierarchical::new(self.base.clone(), refined_itiles) + } + fn boundary(&self) -> Topology { + let base_boundary = self.base.boundary(); + let itiles = self.itiles_levels().zip(refine_iter(base_boundary.clone())).map( + |((itiles, level), blevel)| { + let mut itiles = blevel.tesselation().unapply_indices_from(level.tesselation(), itiles).unwrap(); + itiles.sort_by_key(|&index| index); + itiles + }, + ).collect(); + Hierarchical::new(base_boundary, itiles) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use approx::assert_abs_diff_eq; + + macro_rules! assert_centroids { + ($topo:expr, $geom:expr, $desired:expr $(, $simplex:ident)*) => {{ + let topo = $topo; + let geom = $geom; + let tesselation = topo.tesselation(); + let desired = $desired; + let dim_out = tesselation.dim_out(); + let centroid_in = iter::once([]); + let simplex_dim = 0; + $( + let centroid_in = centroid_in.map(|coord| [&coord as &[f64], &Simplex::$simplex.centroid()].concat()); + let simplex_dim = simplex_dim + Simplex::$simplex.dim(); + )* + assert_eq!(simplex_dim, topo.dim(), "the given simplices don't add up to the dimension of the topology"); + let pad: Vec = iter::repeat(0.0).take(dim_out - topo.dim()).collect(); + let centroid_in: Vec = centroid_in.flat_map(|coord| [&coord[..], &pad].concat()).collect(); + + assert_eq!(desired.len(), topo.ntiles(), "invalid len of desired centroids"); + + for (i, desired) in desired.into_iter().enumerate() { + println!("i = {i}"); + let mut actual = centroid_in.clone(); + let iroot = tesselation.apply_inplace(i, &mut actual, dim_out).unwrap(); + geom(iroot, &mut actual); + assert_abs_diff_eq!(actual[..], desired[..]); + } + }}; + } + + #[test] + fn test1() { + let x0 = Line::from_len(2); + let geom = |i: usize, c: &mut [f64]| c[0] += i as f64; + assert_centroids!(&x0, geom, [[0.5], [1.5]], Line); + let x1 = x0.refined_by(&[1]); + assert_centroids!(&x1, geom, [[0.5], [1.25], [1.75]], Line); + let x2 = x1.refined_by(&[1]); + println!("{:?}", x2.tesselation()); + assert_centroids!(&x2, geom, [[0.5], [1.75], [1.125], [1.375]], Line); + + let x0b = x0.boundary(); + assert_centroids!(&x0b, geom, [[0.0], [2.0]]); + } + + #[test] + fn test2() { + let x = Line::from_len(2); + let y = Line::from_len(2); + let geom = |i: usize, c: &mut [f64]| { + c[0] += (i / 2) as f64; + c[1] += (i % 2) as f64; + }; + let xy = Product::new(x.clone(), y.clone()); + assert_centroids!( + &xy, + geom, + [[0.5, 0.5], [0.5, 1.5], [1.5, 0.5], [1.5, 1.5]], + Line, + Line + ); + assert_centroids!( + &xy.boundary(), + geom, + [ + [0.5, 0.0], + [0.5, 2.0], + [1.5, 0.0], + [1.5, 2.0], + [0.0, 0.5], + [0.0, 1.5], + [2.0, 0.5], + [2.0, 1.5] + ], + Line + ); + } + + #[test] + fn hierarchical() { + let x = Line::from_len(2); + let y = Line::from_len(2); + let geom = |i: usize, c: &mut [f64]| { + c[0] += (i / 2) as f64; + c[1] += (i % 2) as f64; + }; + let xy0 = Product::new(x, y); + assert_centroids!( + &xy0, + geom, + [[0.5, 0.5], [0.5, 1.5], [1.5, 0.5], [1.5, 1.5]], + Line, + Line + ); + assert_centroids!( + xy0.boundary(), + geom, + [ + [0.5, 0.0], + [0.5, 2.0], + [1.5, 0.0], + [1.5, 2.0], + [0.0, 0.5], + [0.0, 1.5], + [2.0, 0.5], + [2.0, 1.5] + ], + Line + ); + let xy1 = xy0.refined_by(&[2]); + assert_centroids!( + &xy1, + geom, + [ + [0.5, 0.5], + [0.5, 1.5], + [1.5, 1.5], + [1.25, 0.25], + [1.25, 0.75], + [1.75, 0.25], + [1.75, 0.75] + ], + Line, + Line + ); + assert_centroids!( + xy1.boundary(), + geom, + [ + [0.5, 0.0], + [0.5, 2.0], + [1.5, 2.0], + [0.0, 0.5], + [0.0, 1.5], + [2.0, 1.5], + [1.25, 0.0], + [1.75, 0.0], + [2.0, 0.25], + [2.0, 0.75], + ], + Line + ); + let xy2 = xy1.refined_by(&[3]); + assert_centroids!( + &xy2, + geom, + [ + [0.5, 0.5], + [0.5, 1.5], + [1.5, 1.5], + [1.25, 0.75], + [1.75, 0.25], + [1.75, 0.75], + [1.125, 0.125], + [1.125, 0.375], + [1.375, 0.125], + [1.375, 0.375] + ], + Line, + Line + ); + } +} From f11dd11bb0bdad16f972d0bacedff52603e5282b Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Fri, 24 Jun 2022 10:39:05 +0200 Subject: [PATCH 19/45] WIP --- src/elementary.rs | 13 +++++++--- src/lib.rs | 2 +- src/ops.rs | 4 +-- src/{relative_to.rs => relative.rs} | 40 ++++++++++++----------------- src/topology.rs | 24 ++++++++--------- 5 files changed, 40 insertions(+), 43 deletions(-) rename src/{relative_to.rs => relative.rs} (92%) diff --git a/src/elementary.rs b/src/elementary.rs index a2be6a04e..967419523 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -537,16 +537,16 @@ macro_rules! dispatch { ( $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( &$self:ident $(, $arg:ident: $ty:ty)* - ) $($ret:tt)* + ) $(-> $ret:ty)? ) => { #[inline] - $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $($ret)* { + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { dispatch!(@match $self; $fn; $($arg),*) } }; - ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { #[inline] - $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { dispatch!(@match $self; $fn; $($arg),*) } }; @@ -617,6 +617,11 @@ impl From for Elementary { pub trait PushElementary { fn push_elementary(&mut self, map: &Elementary); + fn clone_and_push_elementary(&self, map: &Elementary) -> Self where Self: Clone { + let mut cloned = self.clone(); + cloned.push_elementary(map); + cloned + } } impl PushElementary for Vec { diff --git a/src/lib.rs b/src/lib.rs index a503f64f2..bbbfbbf81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ pub mod elementary; pub mod finite_f64; pub mod ops; -pub mod relative_to; +pub mod relative; pub mod simplex; pub mod topology; diff --git a/src/ops.rs b/src/ops.rs index ea3de5135..bf6e41ede 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -269,8 +269,8 @@ impl Concatenation { pub fn iter(&self) -> impl Iterator { self.0.iter() } - pub fn into_iter(self) -> impl Iterator { - self.0.into_iter() + pub fn into_vec(self) -> Vec { + self.0 } } diff --git a/src/relative_to.rs b/src/relative.rs similarity index 92% rename from src/relative_to.rs rename to src/relative.rs index 921989eb2..ce98936f3 100644 --- a/src/relative_to.rs +++ b/src/relative.rs @@ -11,12 +11,12 @@ trait RemoveCommonPrefix: Sized { fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self); } -fn split_heads(items: &[Elementary]) -> BTreeMap, Vec)> { +fn split_heads(items: &[Elementary]) -> BTreeMap<(Option, Elementary), Vec> { let mut heads = BTreeMap::new(); for (i, item) in items.iter().enumerate() { if let Some((transpose, head, mut tail)) = item.shift_left(&items[..i]) { tail.extend(items[i + 1..].iter().cloned()); - heads.insert(head, (transpose, tail)); + heads.insert((transpose, head), tail); } if let Elementary::Edges(Edges(Simplex::Line, offset)) = item { let children = Elementary::new_children(Simplex::Line).with_offset(*offset); @@ -27,7 +27,7 @@ fn split_heads(items: &[Elementary]) -> BTreeMap, Simplex::Line.nedges() * Simplex::Line.nchildren(), )); tail.extend(items[i + 1..].iter().cloned()); - heads.insert(head, (transpose, tail)); + heads.insert((transpose, head), tail); } } } @@ -50,19 +50,16 @@ impl RemoveCommonPrefix for Vec { tail2 = iter2.collect(); continue; } - let heads1: BTreeMap> = split_heads(&tail1) + let heads1 = split_heads(&tail1); + let mut heads2 = split_heads(&tail2); + if let Some(((transpose, head), new_tail1, new_tail2)) = heads1 .into_iter() - .filter_map(|(head, (trans, tail))| trans.is_none().then(|| (head, tail))) - .collect(); - let mut heads2: BTreeMap> = split_heads(&tail2) - .into_iter() - .filter_map(|(head, (trans, tail))| trans.is_none().then(|| (head, tail))) - .collect(); - if let Some((head, new_tail1, new_tail2)) = heads1 - .into_iter() - .filter_map(|(h, t1)| heads2.remove(&h).map(|t2| (h, t1, t2))) + .filter_map(|(key, t1)| heads2.remove(&key).map(|t2| (key, t1, t2))) .min_by_key(|(_, t1, t2)| std::cmp::max(t1.len(), t2.len())) { + if let Some(transpose) = transpose { + common.push(transpose.into()); + } common.push(head); tail1 = new_tail1; tail2 = new_tail2; @@ -111,16 +108,16 @@ macro_rules! dispatch { ( $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( &$self:ident $(, $arg:ident: $ty:ty)* - ) $($ret:tt)* + ) $(-> $ret:ty)? ) => { #[inline] - $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $($ret)* { + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { dispatch!(@match $self; $fn; $($arg),*) } }; - ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { #[inline] - $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { dispatch!(@match $self; $fn; $($arg),*) } }; @@ -292,7 +289,7 @@ impl RelativeTo> for WithBounds> { for target in targets.iter() { let (_, rem, rel) = target .get_unbounded() - .remove_common_prefix_opt_lhs(&self.get_unbounded()); + .remove_common_prefix_opt_lhs(self.get_unbounded()); if rem.is_identity() { let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); let rel: Vec = iter::once(slice).chain(rel).collect(); @@ -336,8 +333,8 @@ impl RelativeTo> for WithBounds> { } rels.push(rel); } - if let Some(index_map) = index_map.into_iter().collect::>>() { - Some(Relative::Multiple(RelativeMultiple { + index_map.into_iter().collect::>>().map(|index_map| + Relative::Multiple(RelativeMultiple { index_map: index_map.into(), rels, common, @@ -346,9 +343,6 @@ impl RelativeTo> for WithBounds> { len_out: targets.len_in(), len_in: self.len_in(), })) - } else { - None - } } } diff --git a/src/topology.rs b/src/topology.rs index ab88a89b4..9eb4fbb95 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -1,8 +1,8 @@ -use crate::elementary::{Elementary, PushElementary}; +use crate::elementary::{Elementary, PushElementary as _}; use crate::ops::{Concatenation, WithBounds}; -use crate::relative_to::RelativeTo; +use crate::relative::RelativeTo as _; use crate::simplex::Simplex; -use crate::BoundedMap; +use crate::BoundedMap as _; use std::iter; use std::rc::Rc; @@ -32,14 +32,14 @@ pub trait TopologyCore: std::fmt::Debug + Clone + Into { fn map_itiles_to_refined(&self, itiles: &[usize]) -> Vec { self.refined() .tesselation() - .unapply_indices_from(self.tesselation(), &itiles) + .unapply_indices_from(self.tesselation(), itiles) .unwrap() } fn boundary(&self) -> Topology; fn take(&self, itiles: &[usize]) -> Topology { let mut itiles: Vec = itiles.to_vec(); itiles.sort_by_key(|&index| index); - Take::new(self.clone().into(), itiles.into()) + Take::new(self.clone().into(), itiles) } fn refined_by(&self, itiles: &[usize]) -> Topology { Hierarchical::new(self.clone().into(), vec![(0..self.ntiles()).collect()]) @@ -61,16 +61,16 @@ macro_rules! dispatch { ( $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( &$self:ident $(, $arg:ident: $ty:ty)* - ) $($ret:tt)* + ) $(-> $ret:ty)? ) => { #[inline] - $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $($ret)* { + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { dispatch!(@match $self; $fn; $($arg),*) } }; - ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $($ret:tt)*) => { + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { #[inline] - $vis fn $fn(&mut $self $(, $arg: $ty)*) $($ret)* { + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { dispatch!(@match $self; $fn; $($arg),*) } }; @@ -294,7 +294,7 @@ impl TopologyCore for Take { .unapply_indices_from(self.topo.tesselation(), &self.itiles) .unwrap(); itiles.sort_by_key(|&index| index); - Take::new(refined, itiles.into()) + Take::new(refined, itiles) } fn boundary(&self) -> Topology { unimplemented! {} @@ -314,20 +314,18 @@ fn refine_iter(base: Topology) -> impl Iterator { impl Hierarchical { pub fn new(base: Topology, itiles: Vec>) -> Topology { - //println!("{itiles:?}"); let tesselation = Concatenation::new( itiles .iter() .zip(refine_iter(base.clone())) .filter(|(itiles, _)| !itiles.is_empty()) .flat_map(|(itiles, level)| { - //println!("{level:?} {itiles:?}"); let mut tesselation = level.tesselation().clone(); tesselation.push_elementary(&Elementary::new_take( itiles.clone(), tesselation.len_in(), )); - tesselation.into_iter() + tesselation.into_vec() }) .collect(), ); From ac5b6c26ebc1ffe75c05eeb8d4053487c929c671 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Mon, 27 Jun 2022 10:49:25 +0200 Subject: [PATCH 20/45] WIP --- src/elementary.rs | 54 ++++++++++---- src/lib.rs | 7 +- src/ops.rs | 58 +++++++++------ src/relative.rs | 24 +++--- src/simplex.rs | 7 ++ src/tesselation.rs | 177 +++++++++++++++++++++++++++++++++++++++++++++ src/topology.rs | 5 +- 7 files changed, 281 insertions(+), 51 deletions(-) create mode 100644 src/tesselation.rs diff --git a/src/elementary.rs b/src/elementary.rs index 967419523..6ad190dca 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -1,6 +1,6 @@ use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; -use crate::{UnapplyIndicesData, UnboundedMap}; +use crate::{AddOffset, UnapplyIndicesData, UnboundedMap}; use num::Integer as _; use std::rc::Rc; @@ -36,7 +36,6 @@ impl UnboundedMap for Identity { fn delta_dim(&self) -> usize { 0 } - fn add_offset(&mut self, _offset: usize) {} fn mod_in(&self) -> usize { 1 } @@ -57,6 +56,10 @@ impl UnboundedMap for Identity { } } +impl AddOffset for Identity { + fn add_offset(&mut self, _offset: usize) {} +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Transpose(usize, usize); @@ -78,7 +81,6 @@ impl UnboundedMap for Transpose { fn delta_dim(&self) -> usize { 0 } - fn add_offset(&mut self, _offset: usize) {} fn mod_in(&self) -> usize { if self.0 != 1 && self.1 != 1 { self.0 * self.1 @@ -109,6 +111,10 @@ impl UnboundedMap for Transpose { } } +impl AddOffset for Transpose { + fn add_offset(&mut self, _offset: usize) {} +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Take { indices: Rc<[usize]>, @@ -138,7 +144,6 @@ impl UnboundedMap for Take { fn delta_dim(&self) -> usize { 0 } - fn add_offset(&mut self, _offset: usize) {} fn mod_in(&self) -> usize { self.nindices } @@ -166,6 +171,10 @@ impl UnboundedMap for Take { } } +impl AddOffset for Take { + fn add_offset(&mut self, _offset: usize) {} +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Slice { start: usize, @@ -191,7 +200,6 @@ impl UnboundedMap for Slice { fn delta_dim(&self) -> usize { 0 } - fn add_offset(&mut self, _offset: usize) {} fn mod_in(&self) -> usize { self.len_in } @@ -217,6 +225,10 @@ impl UnboundedMap for Slice { } } +impl AddOffset for Slice { + fn add_offset(&mut self, _offset: usize) {} +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Children(Simplex, usize); @@ -233,9 +245,6 @@ impl UnboundedMap for Children { fn delta_dim(&self) -> usize { 0 } - fn add_offset(&mut self, offset: usize) { - self.1 += offset; - } fn mod_in(&self) -> usize { self.0.nchildren() } @@ -256,6 +265,12 @@ impl UnboundedMap for Children { } } +impl AddOffset for Children { + fn add_offset(&mut self, offset: usize) { + self.1 += offset; + } +} + impl From for Children { fn from(simplex: Simplex) -> Children { Children::new(simplex) @@ -278,9 +293,6 @@ impl UnboundedMap for Edges { fn delta_dim(&self) -> usize { 1 } - fn add_offset(&mut self, offset: usize) { - self.1 += offset; - } fn mod_in(&self) -> usize { self.0.nedges() } @@ -301,6 +313,12 @@ impl UnboundedMap for Edges { } } +impl AddOffset for Edges { + fn add_offset(&mut self, offset: usize) { + self.1 += offset; + } +} + impl From for Edges { fn from(simplex: Simplex) -> Edges { Edges::new(simplex) @@ -337,9 +355,6 @@ impl UnboundedMap for UniformPoints { fn delta_dim(&self) -> usize { self.point_dim } - fn add_offset(&mut self, offset: usize) { - self.offset += offset; - } fn mod_in(&self) -> usize { self.npoints } @@ -365,6 +380,12 @@ impl UnboundedMap for UniformPoints { } } +impl AddOffset for UniformPoints { + fn add_offset(&mut self, offset: usize) { + self.offset += offset; + } +} + #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum Elementary { Transpose(Transpose), @@ -565,7 +586,6 @@ macro_rules! dispatch { impl UnboundedMap for Elementary { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} - dispatch! {fn add_offset(&mut self, offset: usize)} dispatch! {fn mod_in(&self) -> usize} dispatch! {fn mod_out(&self) -> usize} dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize} @@ -575,6 +595,10 @@ impl UnboundedMap for Elementary { dispatch! {fn is_identity(&self) -> bool} } +impl AddOffset for Elementary { + dispatch! {fn add_offset(&mut self, offset: usize)} +} + impl std::fmt::Debug for Elementary { dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result} } diff --git a/src/lib.rs b/src/lib.rs index bbbfbbf81..6922c0bf5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod ops; pub mod relative; pub mod simplex; pub mod topology; +pub mod tesselation; pub trait BoundedMap { fn len_out(&self) -> usize; @@ -13,7 +14,6 @@ pub trait BoundedMap { } fn dim_in(&self) -> usize; fn delta_dim(&self) -> usize; - fn add_offset(&mut self, offset: usize); fn apply_inplace_unchecked( &self, index: usize, @@ -71,7 +71,6 @@ pub trait UnboundedMap { } // Difference in dimension of the output and input coordinate. fn delta_dim(&self) -> usize; - fn add_offset(&mut self, offset: usize); // Modulus of the input index. The map repeats itself at index `mod_in` // and the output index is incremented with `in_index / mod_in * mod_out`. fn mod_in(&self) -> usize; @@ -90,6 +89,10 @@ pub trait UnboundedMap { } } +pub trait AddOffset { + fn add_offset(&mut self, offset: usize); +} + pub trait UnapplyIndicesData: Clone + std::fmt::Debug { fn last(&self) -> usize; fn push(&self, index: usize) -> Self; diff --git a/src/ops.rs b/src/ops.rs index bf6e41ede..e889ac410 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,5 +1,5 @@ use crate::elementary::{Elementary, PushElementary}; -use crate::{BoundedMap, UnapplyIndicesData, UnboundedMap}; +use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; use num::Integer as _; use std::ops::{Deref, DerefMut}; @@ -72,10 +72,6 @@ impl BoundedMap for WithBounds { fn delta_dim(&self) -> usize { self.delta_dim } - fn add_offset(&mut self, offset: usize) { - self.map.add_offset(offset); - self.dim_in += offset; - } fn apply_inplace_unchecked( &self, index: usize, @@ -98,6 +94,13 @@ impl BoundedMap for WithBounds { } } +impl AddOffset for WithBounds { + fn add_offset(&mut self, offset: usize) { + self.map.add_offset(offset); + self.dim_in += offset; + } +} + impl PushElementary for WithBounds { fn push_elementary(&mut self, item: &Elementary) { // TODO: return an error if we push something that causes `dim_in` to drop below zero @@ -158,10 +161,6 @@ impl BoundedMap for Composition usize { self.inner.delta_dim() + self.outer.delta_dim() } - fn add_offset(&mut self, offset: usize) { - self.inner.add_offset(offset); - self.outer.add_offset(offset); - } fn apply_inplace_unchecked( &self, index: usize, @@ -204,10 +203,6 @@ impl UnboundedMap for Composition usize { self.inner.delta_dim() + self.outer.delta_dim() } - fn add_offset(&mut self, offset: usize) { - self.inner.add_offset(offset); - self.outer.add_offset(offset); - } fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { let index = self.inner.apply_inplace(index, coordinates, stride); self.outer.apply_inplace(index, coordinates, stride) @@ -229,6 +224,13 @@ impl UnboundedMap for Composition AddOffset for Composition { + fn add_offset(&mut self, offset: usize) { + self.inner.add_offset(offset); + self.outer.add_offset(offset); + } +} + pub trait Compose: BoundedMap + Sized { fn compose( self, @@ -287,11 +289,6 @@ impl BoundedMap for Concatenation { fn len_in(&self) -> usize { self.iter().map(|item| item.len_in()).sum() } - fn add_offset(&mut self, offset: usize) { - for item in &mut self.0 { - item.add_offset(offset); - } - } fn apply_inplace_unchecked( &self, index: usize, @@ -323,6 +320,14 @@ impl BoundedMap for Concatenation { } } +impl AddOffset for Concatenation { + fn add_offset(&mut self, offset: usize) { + for item in &mut self.0 { + item.add_offset(offset); + } + } +} + impl PushElementary for Concatenation where M: BoundedMap + PushElementary, @@ -389,11 +394,6 @@ where fn delta_dim(&self) -> usize { self.iter().map(|item| item.delta_dim()).sum() } - fn add_offset(&mut self, offset: usize) { - for item in self.iter_mut() { - item.add_offset(offset); - } - } fn mod_in(&self) -> usize { self.deref() .iter() @@ -433,3 +433,15 @@ where }) } } + +impl AddOffset for Array +where + Item: AddOffset, + Array: Deref + DerefMut + std::fmt::Debug, +{ + fn add_offset(&mut self, offset: usize) { + for item in self.iter_mut() { + item.add_offset(offset); + } + } +} diff --git a/src/relative.rs b/src/relative.rs index ce98936f3..d495a4503 100644 --- a/src/relative.rs +++ b/src/relative.rs @@ -1,7 +1,7 @@ use crate::elementary::{Edges, Elementary, Identity, Transpose}; use crate::ops::{Concatenation, WithBounds}; use crate::simplex::Simplex; -use crate::{BoundedMap, UnapplyIndicesData, UnboundedMap}; +use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; use std::collections::BTreeMap; use std::iter; use std::rc::Rc; @@ -137,7 +137,6 @@ impl BoundedMap for Relative { dispatch! {fn dim_out(&self) -> usize} dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} - dispatch! {fn add_offset(&mut self, offset: usize)} dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize} dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> Option} dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} @@ -149,6 +148,10 @@ impl BoundedMap for Relative { dispatch! {fn is_identity(&self) -> bool} } +impl AddOffset for Relative { + dispatch! {fn add_offset(&mut self, offset: usize)} +} + pub trait RelativeTo { fn relative_to(&self, target: &Target) -> Option; fn unapply_indices_from( @@ -234,13 +237,6 @@ impl BoundedMap for RelativeMultiple { fn len_out(&self) -> usize { self.len_out } - fn add_offset(&mut self, offset: usize) { - self.common.add_offset(offset); - for rel in self.rels.iter_mut() { - rel.add_offset(offset); - } - self.dim_in += offset; - } fn apply_inplace_unchecked( &self, index: usize, @@ -282,6 +278,16 @@ impl BoundedMap for RelativeMultiple { } } +impl AddOffset for RelativeMultiple { + fn add_offset(&mut self, offset: usize) { + self.common.add_offset(offset); + for rel in self.rels.iter_mut() { + rel.add_offset(offset); + } + self.dim_in += offset; + } +} + impl RelativeTo> for WithBounds> { fn relative_to(&self, targets: &Concatenation) -> Option { let mut rels_indices = Vec::new(); diff --git a/src/simplex.rs b/src/simplex.rs index 0b7d2c109..cb9e2d345 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -19,6 +19,13 @@ impl Simplex { pub const fn edge_dim(&self) -> usize { self.dim() - 1 } + /// Returns the edge simplex, if any. + pub const fn edge_simplex(&self) -> Option { + match self { + Self::Line => None, + Self::Triangle => Some(Self::Line), + } + } /// Returns the number of edges of the simplex. #[inline] pub const fn nedges(&self) -> usize { diff --git a/src/tesselation.rs b/src/tesselation.rs new file mode 100644 index 000000000..71d803b74 --- /dev/null +++ b/src/tesselation.rs @@ -0,0 +1,177 @@ +use crate::{UnboundedMap, BoundedMap, UnapplyIndicesData, AddOffset}; +use crate::ops::{Concatenation, WithBoundsError}; +use crate::elementary::{Elementary, PushElementary}; +use crate::simplex::Simplex; + +#[derive(Debug, Clone, PartialEq)] +pub struct WithShape { + map: M, + shapes: Vec, + dim_in: usize, + delta_dim: usize, + len_out: usize, + len_in: usize, +} + +impl WithShape { + pub fn new(map: M, shapes: Vec, len_in: usize) -> Result { + let dim_in: usize = shapes.iter().map(|simplex| simplex.dim()).sum(); + if dim_in < map.dim_in() { + Err(WithBoundsError::DimensionTooSmall) + } else if len_in != 0 && len_in.checked_rem(map.mod_in()) != Some(0) { + Err(WithBoundsError::LengthNotAMultipleOfRepetition) + } else { + Ok(Self::new_unchecked(map, shapes, len_in)) + } + } + pub(crate) fn new_unchecked(map: M, shapes: Vec, len_in: usize) -> Self { + let dim_in: usize = shapes.iter().map(|simplex| simplex.dim()).sum(); + let delta_dim = map.delta_dim(); + let len_out = if len_in == 0 { + 0 + } else { + len_in / map.mod_in() * map.mod_out() + }; + Self { + map, + shapes, + dim_in, + delta_dim, + len_out, + len_in, + } + } + pub fn get_unbounded(&self) -> &M { + &self.map + } + pub fn into_unbounded(self) -> M { + self.map + } + pub fn children(&self) -> Self { + let mut map = self.map.clone(); + let mut offset = 0; + let mut len_in = self.len_in; + for simplex in &self.shapes { + let mut children = Elementary::new_children(*simplex); + children.add_offset(offset); + map.push_elementary(&children); + len_in *= simplex.nchildren(); + } + Self::new_unchecked(map, self.shapes.clone(), len_in) + } + pub fn edges(&self) -> Self { + let mut map = self.map.clone(); + let mut offset = 0; + let mut len_in = self.len_in; + let mut shapes = Vec::new(); + for simplex in &self.shapes { + let mut edges = Elementary::new_edges(*simplex); + edges.add_offset(offset); + map.push_elementary(&edges); + len_in *= simplex.nedges(); + if let Some(edge_simplex) = simplex.edge_simplex() { + shapes.push(edge_simplex); + } + } + Self::new_unchecked(map, shapes, len_in) + } +} + +impl BoundedMap for WithShape { + fn len_in(&self) -> usize { + self.len_in + } + fn len_out(&self) -> usize { + self.len_out + } + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn apply_inplace_unchecked( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + ) -> usize { + self.map.apply_inplace(index, coordinates, stride) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + self.map.apply_index(index) + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.map.apply_indices_inplace(indices) + } + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + self.map.unapply_indices(indices) + } + fn is_identity(&self) -> bool { + self.map.is_identity() + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Tesselation(Concatenation>>); + +impl Tesselation { + pub fn new_identity(shapes: Vec, len: usize) -> Self { + Self(Concatenation::new(vec![WithShape::new_unchecked(vec![], shapes, len)])) + } + pub fn iter(&self) -> impl Iterator>> { + self.0.iter() + } + pub fn into_vec(self) -> Vec>> { + self.0.into_vec() + } + pub fn take(&self, indices: &[usize]) -> Self { + unimplemented!{} + } + pub fn children(&self) -> Result { + unimplemented!{} + } + pub fn edges(&self) -> Result { + unimplemented!{} + } + pub fn internal_edges_of_children(&self) -> Result { + unimplemented!{} + } +} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $(-> $ret:ty)? + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; +} + +impl BoundedMap for Tesselation { + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> Option} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} +} + diff --git a/src/topology.rs b/src/topology.rs index 9eb4fbb95..fa09e0f22 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -1,10 +1,11 @@ use crate::elementary::{Elementary, PushElementary as _}; -use crate::ops::{Concatenation, WithBounds}; +use crate::ops::{Concatenation, WithBounds, WithBoundsError}; use crate::relative::RelativeTo as _; use crate::simplex::Simplex; -use crate::BoundedMap as _; +use crate::{AddOffset, UnboundedMap, BoundedMap, UnapplyIndicesData}; use std::iter; use std::rc::Rc; +use std::ops::{Deref, DerefMut}; type Tesselation = Concatenation>>; From f50b8985881de9a65131ef77518b54dbcff02cc4 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Mon, 27 Jun 2022 13:36:17 +0200 Subject: [PATCH 21/45] WIP --- src/elementary.rs | 81 ++++++++++++++++++++++++++++++++--------- src/lib.rs | 23 +++++++++--- src/ops.rs | 91 +++++++++++++++++++++++++++++++++++++++++----- src/relative.rs | 39 ++++++++++++-------- src/tesselation.rs | 26 +++++++------ src/topology.rs | 21 +++++++---- 6 files changed, 214 insertions(+), 67 deletions(-) diff --git a/src/elementary.rs b/src/elementary.rs index 6ad190dca..4a62a367e 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -42,7 +42,13 @@ impl UnboundedMap for Identity { fn mod_out(&self) -> usize { 1 } - fn apply_inplace(&self, index: usize, _coordinates: &mut [f64], _stride: usize) -> usize { + fn apply_inplace( + &self, + index: usize, + _coordinates: &mut [f64], + _stride: usize, + _offset: usize, + ) -> usize { index } fn apply_index(&self, index: usize) -> usize { @@ -91,7 +97,13 @@ impl UnboundedMap for Transpose { fn mod_out(&self) -> usize { self.mod_in() } - fn apply_inplace(&self, index: usize, _coordinates: &mut [f64], _stride: usize) -> usize { + fn apply_inplace( + &self, + index: usize, + _coordinates: &mut [f64], + _stride: usize, + _offset: usize, + ) -> usize { self.apply_index(index) } fn apply_index(&self, index: usize) -> usize { @@ -150,7 +162,13 @@ impl UnboundedMap for Take { fn mod_out(&self) -> usize { self.len } - fn apply_inplace(&self, index: usize, _coordinates: &mut [f64], _stride: usize) -> usize { + fn apply_inplace( + &self, + index: usize, + _coordinates: &mut [f64], + _stride: usize, + _offset: usize, + ) -> usize { self.apply_index(index) } fn apply_index(&self, index: usize) -> usize { @@ -206,7 +224,13 @@ impl UnboundedMap for Slice { fn mod_out(&self) -> usize { self.len_out } - fn apply_inplace(&self, index: usize, _coordinates: &mut [f64], _stride: usize) -> usize { + fn apply_inplace( + &self, + index: usize, + _coordinates: &mut [f64], + _stride: usize, + _offset: usize, + ) -> usize { self.apply_index(index) } fn apply_index(&self, index: usize) -> usize { @@ -251,8 +275,15 @@ impl UnboundedMap for Children { fn mod_out(&self) -> usize { 1 } - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { - self.0.apply_child(index, coordinates, stride, self.1) + fn apply_inplace( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { + self.0 + .apply_child(index, coordinates, stride, offset + self.1) } fn apply_index(&self, index: usize) -> usize { self.0.apply_child_index(index) @@ -299,8 +330,15 @@ impl UnboundedMap for Edges { fn mod_out(&self) -> usize { 1 } - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { - self.0.apply_edge(index, coordinates, stride, self.1) + fn apply_inplace( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { + self.0 + .apply_edge(index, coordinates, stride, offset + self.1) } fn apply_index(&self, index: usize) -> usize { self.0.apply_edge_index(index) @@ -361,10 +399,16 @@ impl UnboundedMap for UniformPoints { fn mod_out(&self) -> usize { 1 } - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { + fn apply_inplace( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { let points: &[f64] = unsafe { std::mem::transmute(&self.points[..]) }; let point = &points[(index % self.npoints) * self.point_dim..][..self.point_dim]; - for coord in coordinates_iter_mut(coordinates, stride, self.offset, self.point_dim, 0) { + for coord in coordinates_iter_mut(coords, stride, offset + self.offset, self.point_dim, 0) { coord.copy_from_slice(point); } index / self.npoints @@ -588,7 +632,7 @@ impl UnboundedMap for Elementary { dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn mod_in(&self) -> usize} dispatch! {fn mod_out(&self) -> usize} - dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize, offset: usize) -> usize} dispatch! {fn apply_index(&self, index: usize) -> usize} dispatch! {fn apply_indices_inplace(&self, indices: &mut [usize])} dispatch! {fn unapply_indices(&self, indices: &[T]) -> Vec} @@ -641,7 +685,10 @@ impl From for Elementary { pub trait PushElementary { fn push_elementary(&mut self, map: &Elementary); - fn clone_and_push_elementary(&self, map: &Elementary) -> Self where Self: Clone { + fn clone_and_push_elementary(&self, map: &Elementary) -> Self + where + Self: Clone, + { let mut cloned = self.clone(); cloned.push_elementary(map); cloned @@ -680,7 +727,7 @@ mod tests { work[..incoord.len()].copy_from_slice(incoord); } } - assert_eq!(item.apply_inplace($inidx, &mut work, stride), $outidx); + assert_eq!(item.apply_inplace($inidx, &mut work, stride, 0), $outidx); for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { assert_abs_diff_eq!(actual[..], desired[..]); } @@ -690,7 +737,7 @@ mod tests { let item = $item.borrow(); let mut work = Vec::with_capacity(0); assert_eq!( - item.apply_inplace($inidx, &mut work, item.dim_out()), + item.apply_inplace($inidx, &mut work, item.dim_out(), 0), $outidx ); }}; @@ -769,7 +816,7 @@ mod tests { let mut map: Vec> = (0..nout).map(|_| Vec::new()).collect(); let mut work = Vec::with_capacity(0); for i in 0..nin { - map[item.apply_inplace(i, &mut work, item.dim_out())].push(i); + map[item.apply_inplace(i, &mut work, item.dim_out(), 0)].push(i); } for (j, desired) in map.into_iter().enumerate() { let mut actual = item.unapply_indices(&[j]); @@ -858,8 +905,8 @@ mod tests { for itip in 0..2 * tip_len { let mut crds_a = coords.clone(); let mut crds_b = coords.clone(); - let iroot_a = a.iter().rev().fold(itip, |i, item| item.apply_inplace(i, &mut crds_a, root_dim)); - let iroot_b = b.iter().rev().fold(itip, |i, item| item.apply_inplace(i, &mut crds_b, root_dim)); + let iroot_a = a.iter().rev().fold(itip, |i, item| item.apply_inplace(i, &mut crds_a, root_dim, 0)); + let iroot_b = b.iter().rev().fold(itip, |i, item| item.apply_inplace(i, &mut crds_b, root_dim, 0)); assert_eq!(iroot_a, iroot_b, "itip={itip}"); assert_abs_diff_eq!(crds_a[..], crds_b[..]); } diff --git a/src/lib.rs b/src/lib.rs index 6922c0bf5..572a6ace9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,8 @@ pub mod finite_f64; pub mod ops; pub mod relative; pub mod simplex; -pub mod topology; pub mod tesselation; +pub mod topology; pub trait BoundedMap { fn len_out(&self) -> usize; @@ -19,10 +19,17 @@ pub trait BoundedMap { index: usize, coordinates: &mut [f64], stride: usize, + offset: usize, ) -> usize; - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> Option { - if index < self.len_in() { - Some(self.apply_inplace_unchecked(index, coordinates, stride)) + fn apply_inplace( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> Option { + if index < self.len_in() && offset + self.dim_out() <= stride { + Some(self.apply_inplace_unchecked(index, coordinates, stride, offset)) } else { None } @@ -76,7 +83,13 @@ pub trait UnboundedMap { fn mod_in(&self) -> usize; // Modulus if the output index. fn mod_out(&self) -> usize; - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize; + fn apply_inplace( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize; fn apply_index(&self, index: usize) -> usize; fn apply_indices_inplace(&self, indices: &mut [usize]) { for index in indices.iter_mut() { diff --git a/src/ops.rs b/src/ops.rs index e889ac410..7655e7f2e 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,4 +1,4 @@ -use crate::elementary::{Elementary, PushElementary}; +use crate::elementary::{Elementary, PushElementary, Transpose}; use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; use num::Integer as _; use std::ops::{Deref, DerefMut}; @@ -77,8 +77,9 @@ impl BoundedMap for WithBounds { index: usize, coordinates: &mut [f64], stride: usize, + offset: usize, ) -> usize { - self.map.apply_inplace(index, coordinates, stride) + self.map.apply_inplace(index, coordinates, stride, offset) } fn apply_index_unchecked(&self, index: usize) -> usize { self.map.apply_index(index) @@ -166,12 +167,13 @@ impl BoundedMap for Composition usize { let index = self .inner - .apply_inplace_unchecked(index, coordinates, stride); + .apply_inplace_unchecked(index, coordinates, stride, offset); self.outer - .apply_inplace_unchecked(index, coordinates, stride) + .apply_inplace_unchecked(index, coordinates, stride, offset) } fn apply_index_unchecked(&self, index: usize) -> usize { let index = self.inner.apply_index_unchecked(index); @@ -203,9 +205,15 @@ impl UnboundedMap for Composition usize { self.inner.delta_dim() + self.outer.delta_dim() } - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { - let index = self.inner.apply_inplace(index, coordinates, stride); - self.outer.apply_inplace(index, coordinates, stride) + fn apply_inplace( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { + let index = self.inner.apply_inplace(index, coordinates, stride, offset); + self.outer.apply_inplace(index, coordinates, stride, offset) } fn apply_index(&self, index: usize) -> usize { let index = self.inner.apply_index(index); @@ -294,9 +302,10 @@ impl BoundedMap for Concatenation { index: usize, coordinates: &mut [f64], stride: usize, + offset: usize, ) -> usize { let (item, index) = self.resolve_item_unchecked(index); - item.apply_inplace_unchecked(index, coordinates, stride) + item.apply_inplace_unchecked(index, coordinates, stride, offset) } fn apply_index_unchecked(&self, index: usize) -> usize { let (item, index) = self.resolve_item_unchecked(index); @@ -361,6 +370,62 @@ where } } +struct Product(M0, M1); + +impl BoundedMap for Product { + fn dim_in(&self) -> usize { + self.0.dim_in() + self.1.dim_in() + } + fn delta_dim(&self) -> usize { + self.0.delta_dim() + self.1.delta_dim() + } + fn len_out(&self) -> usize { + self.0.len_out() * self.1.len_out() + } + fn len_in(&self) -> usize { + self.0.len_in() * self.1.len_in() + } + fn apply_inplace_unchecked( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { + let index = self + .1 + .apply_inplace_unchecked(index, coords, stride, offset + self.0.dim_in()); + let index = Transpose::new(self.0.len_in(), self.1.len_in()).apply_index(index); + let index = self + .0 + .apply_inplace_unchecked(index, coords, stride, offset); + Transpose::new(self.1.len_in(), self.0.len_in()).apply_index(index) + } + fn apply_index_unchecked(&self, index: usize) -> usize { + let index = self.1.apply_index_unchecked(index); + let index = Transpose::new(self.0.len_in(), self.1.len_in()).apply_index(index); + let index = self.0.apply_index_unchecked(index); + Transpose::new(self.1.len_in(), self.0.len_in()).apply_index(index) + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.1.apply_indices_inplace_unchecked(indices); + Transpose::new(self.0.len_in(), self.1.len_in()).apply_indices_inplace(indices); + self.0.apply_indices_inplace_unchecked(indices); + Transpose::new(self.1.len_in(), self.0.len_in()).apply_indices_inplace(indices); + } + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + let indices = + Transpose::new(self.1.len_in(), self.0.len_in()).unapply_indices(indices); + let indices = self.0.unapply_indices_unchecked(&indices); + let indices = + Transpose::new(self.0.len_in(), self.1.len_in()).unapply_indices(&indices); + self.1.unapply_indices_unchecked(&indices) + } + fn is_identity(&self) -> bool { + self.0.is_identity() && self.1.is_identity() + } +} + #[inline] fn update_dim_out_in(map: &M, dim_out: usize, dim_in: usize) -> (usize, usize) { if let Some(n) = map.dim_in().checked_sub(dim_out) { @@ -412,9 +477,15 @@ where }) .0 } - fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize { + fn apply_inplace( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { self.iter().rev().fold(index, |index, item| { - item.apply_inplace(index, coordinates, stride) + item.apply_inplace(index, coordinates, stride, offset) }) } fn apply_index(&self, index: usize) -> usize { diff --git a/src/relative.rs b/src/relative.rs index d495a4503..7fdb3ac25 100644 --- a/src/relative.rs +++ b/src/relative.rs @@ -137,8 +137,8 @@ impl BoundedMap for Relative { dispatch! {fn dim_out(&self) -> usize} dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} - dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize} - dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> Option} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} dispatch! {fn apply_index(&self, index: usize) -> Option} dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} @@ -242,11 +242,14 @@ impl BoundedMap for RelativeMultiple { index: usize, coordinates: &mut [f64], stride: usize, + offset: usize, ) -> usize { - let index = self.common.apply_inplace(index, coordinates, stride); + let index = self + .common + .apply_inplace(index, coordinates, stride, offset); let (iout, iin) = self.index_map[index]; let n = self.index_map.len(); - self.rels[iin / n].apply_inplace(iin % n, coordinates, stride); + self.rels[iin / n].apply_inplace(iin % n, coordinates, stride, offset); iout } fn apply_index_unchecked(&self, index: usize) -> usize { @@ -339,16 +342,20 @@ impl RelativeTo> for WithBounds> { } rels.push(rel); } - index_map.into_iter().collect::>>().map(|index_map| - Relative::Multiple(RelativeMultiple { - index_map: index_map.into(), - rels, - common, - delta_dim: targets.dim_in() - self.dim_in(), - dim_in: self.dim_in(), - len_out: targets.len_in(), - len_in: self.len_in(), - })) + index_map + .into_iter() + .collect::>>() + .map(|index_map| { + Relative::Multiple(RelativeMultiple { + index_map: index_map.into(), + rels, + common, + delta_dim: targets.dim_in() - self.dim_in(), + dim_in: self.dim_in(), + len_out: targets.len_in(), + len_in: self.len_in(), + }) + }) } } @@ -413,8 +420,8 @@ mod tests { for i in 0..2 * a.len_in() { let mut crds_a = coords.clone(); let mut crds_b = coords.clone(); - let ja = a.apply_inplace(i, &mut crds_a, a.dim_out()); - let jb = b.apply_inplace(i, &mut crds_b, a.dim_out()); + let ja = a.apply_inplace(i, &mut crds_a, a.dim_out(), 0); + let jb = b.apply_inplace(i, &mut crds_b, a.dim_out(), 0); assert_eq!(ja, jb, "i={i}"); assert_abs_diff_eq!(crds_a[..], crds_b[..]); } diff --git a/src/tesselation.rs b/src/tesselation.rs index 71d803b74..9a1c35dd2 100644 --- a/src/tesselation.rs +++ b/src/tesselation.rs @@ -1,7 +1,7 @@ -use crate::{UnboundedMap, BoundedMap, UnapplyIndicesData, AddOffset}; -use crate::ops::{Concatenation, WithBoundsError}; use crate::elementary::{Elementary, PushElementary}; +use crate::ops::{Concatenation, WithBoundsError}; use crate::simplex::Simplex; +use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; #[derive(Debug, Clone, PartialEq)] pub struct WithShape { @@ -95,8 +95,9 @@ impl BoundedMap for WithShape { index: usize, coordinates: &mut [f64], stride: usize, + offset: usize, ) -> usize { - self.map.apply_inplace(index, coordinates, stride) + self.map.apply_inplace(index, coordinates, stride, offset) } fn apply_index_unchecked(&self, index: usize) -> usize { self.map.apply_index(index) @@ -117,7 +118,11 @@ pub struct Tesselation(Concatenation>>); impl Tesselation { pub fn new_identity(shapes: Vec, len: usize) -> Self { - Self(Concatenation::new(vec![WithShape::new_unchecked(vec![], shapes, len)])) + Self(Concatenation::new(vec![WithShape::new_unchecked( + vec![], + shapes, + len, + )])) } pub fn iter(&self) -> impl Iterator>> { self.0.iter() @@ -126,16 +131,16 @@ impl Tesselation { self.0.into_vec() } pub fn take(&self, indices: &[usize]) -> Self { - unimplemented!{} + unimplemented! {} } pub fn children(&self) -> Result { - unimplemented!{} + unimplemented! {} } pub fn edges(&self) -> Result { - unimplemented!{} + unimplemented! {} } pub fn internal_edges_of_children(&self) -> Result { - unimplemented!{} + unimplemented! {} } } @@ -164,8 +169,8 @@ impl BoundedMap for Tesselation { dispatch! {fn dim_out(&self) -> usize} dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} - dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize) -> usize} - dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize) -> Option} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} dispatch! {fn apply_index(&self, index: usize) -> Option} dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} @@ -174,4 +179,3 @@ impl BoundedMap for Tesselation { dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} dispatch! {fn is_identity(&self) -> bool} } - diff --git a/src/topology.rs b/src/topology.rs index fa09e0f22..b49b10f8e 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -2,10 +2,10 @@ use crate::elementary::{Elementary, PushElementary as _}; use crate::ops::{Concatenation, WithBounds, WithBoundsError}; use crate::relative::RelativeTo as _; use crate::simplex::Simplex; -use crate::{AddOffset, UnboundedMap, BoundedMap, UnapplyIndicesData}; +use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; use std::iter; -use std::rc::Rc; use std::ops::{Deref, DerefMut}; +use std::rc::Rc; type Tesselation = Concatenation>>; @@ -384,13 +384,18 @@ impl TopologyCore for Hierarchical { } fn boundary(&self) -> Topology { let base_boundary = self.base.boundary(); - let itiles = self.itiles_levels().zip(refine_iter(base_boundary.clone())).map( - |((itiles, level), blevel)| { - let mut itiles = blevel.tesselation().unapply_indices_from(level.tesselation(), itiles).unwrap(); + let itiles = self + .itiles_levels() + .zip(refine_iter(base_boundary.clone())) + .map(|((itiles, level), blevel)| { + let mut itiles = blevel + .tesselation() + .unapply_indices_from(level.tesselation(), itiles) + .unwrap(); itiles.sort_by_key(|&index| index); itiles - }, - ).collect(); + }) + .collect(); Hierarchical::new(base_boundary, itiles) } } @@ -422,7 +427,7 @@ mod tests { for (i, desired) in desired.into_iter().enumerate() { println!("i = {i}"); let mut actual = centroid_in.clone(); - let iroot = tesselation.apply_inplace(i, &mut actual, dim_out).unwrap(); + let iroot = tesselation.apply_inplace(i, &mut actual, dim_out, 0).unwrap(); geom(iroot, &mut actual); assert_abs_diff_eq!(actual[..], desired[..]); } From 419ddee19fa8e760f5e3aed92293851be9b76823 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Tue, 28 Jun 2022 14:13:53 +0200 Subject: [PATCH 22/45] WIP --- src/elementary.rs | 155 +++--- src/lib.rs | 123 ++--- src/ops.rs | 1166 ++++++++++++++++++++++++++++++-------------- src/simplex.rs | 14 +- src/tesselation.rs | 368 ++++++++------ 5 files changed, 1135 insertions(+), 691 deletions(-) diff --git a/src/elementary.rs b/src/elementary.rs index 4a62a367e..7738145f7 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -1,6 +1,6 @@ use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; -use crate::{AddOffset, UnapplyIndicesData, UnboundedMap}; +use crate::{AddOffset, Map, UnapplyIndicesData}; use num::Integer as _; use std::rc::Rc; @@ -29,7 +29,7 @@ fn coordinates_iter_mut( #[derive(Debug, Clone, Copy, PartialEq)] pub struct Identity; -impl UnboundedMap for Identity { +impl Map for Identity { fn dim_in(&self) -> usize { 0 } @@ -54,6 +54,7 @@ impl UnboundedMap for Identity { fn apply_index(&self, index: usize) -> usize { index } + fn apply_indices_inplace(&self, _indices: &mut [usize]) {} fn unapply_indices(&self, indices: &[T]) -> Vec { indices.to_vec() } @@ -80,7 +81,7 @@ impl Transpose { } } -impl UnboundedMap for Transpose { +impl Map for Transpose { fn dim_in(&self) -> usize { 0 } @@ -115,9 +116,9 @@ impl UnboundedMap for Transpose { indices .iter() .map(|index| { - let (j, k) = divmod(index.last(), self.0); + let (j, k) = divmod(index.get(), self.0); let (i, j) = divmod(j, self.1); - index.push((i * self.0 + k) * self.1 + j) + index.set((i * self.0 + k) * self.1 + j) }) .collect() } @@ -149,7 +150,7 @@ impl Take { } } -impl UnboundedMap for Take { +impl Map for Take { fn dim_in(&self) -> usize { 0 } @@ -178,12 +179,12 @@ impl UnboundedMap for Take { indices .iter() .filter_map(|index| { - let (j, iout) = divmod(index.last(), self.len); + let (j, iout) = divmod(index.get(), self.len); let offset = j * self.nindices; self.indices .iter() .position(|i| *i == iout) - .map(|iin| index.push(offset + iin)) + .map(|iin| index.set(offset + iin)) }) .collect() } @@ -211,7 +212,7 @@ impl Slice { } } -impl UnboundedMap for Slice { +impl Map for Slice { fn dim_in(&self) -> usize { 0 } @@ -240,10 +241,10 @@ impl UnboundedMap for Slice { indices .iter() .filter_map(|index| { - let (j, i) = divmod(index.last(), self.len_out); + let (j, i) = divmod(index.get(), self.len_out); (self.start..self.start + self.len_in) .contains(&i) - .then(|| index.push(i - self.start + j * self.len_in)) + .then(|| index.set(i - self.start + j * self.len_in)) }) .collect() } @@ -262,7 +263,7 @@ impl Children { } } -impl UnboundedMap for Children { +impl Map for Children { fn dim_in(&self) -> usize { self.0.dim() + self.1 } @@ -288,10 +289,13 @@ impl UnboundedMap for Children { fn apply_index(&self, index: usize) -> usize { self.0.apply_child_index(index) } + fn apply_indices_inplace(&self, indices: &mut [usize]) { + self.0.apply_child_indices_inplace(indices) + } fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() - .flat_map(|i| (0..self.mod_in()).map(move |j| i.push(i.last() * self.mod_in() + j))) + .flat_map(|i| (0..self.mod_in()).map(move |j| i.set(i.get() * self.mod_in() + j))) .collect() } } @@ -317,7 +321,7 @@ impl Edges { } } -impl UnboundedMap for Edges { +impl Map for Edges { fn dim_in(&self) -> usize { self.0.edge_dim() + self.1 } @@ -343,10 +347,13 @@ impl UnboundedMap for Edges { fn apply_index(&self, index: usize) -> usize { self.0.apply_edge_index(index) } + fn apply_indices_inplace(&self, indices: &mut [usize]) { + self.0.apply_edge_indices_inplace(indices) + } fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() - .flat_map(|i| (0..self.mod_in()).map(move |j| i.push(i.last() * self.mod_in() + j))) + .flat_map(|i| (0..self.mod_in()).map(move |j| i.set(i.get() * self.mod_in() + j))) .collect() } } @@ -386,7 +393,7 @@ impl UniformPoints { } } -impl UnboundedMap for UniformPoints { +impl Map for UniformPoints { fn dim_in(&self) -> usize { self.offset } @@ -416,10 +423,13 @@ impl UnboundedMap for UniformPoints { fn apply_index(&self, index: usize) -> usize { index / self.npoints } + fn apply_indices_inplace(&self, indices: &mut [usize]) { + indices.iter_mut().for_each(|i| *i /= self.npoints) + } fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() - .flat_map(|i| (0..self.mod_in()).map(move |j| i.push(i.last() * self.mod_in() + j))) + .flat_map(|i| (0..self.mod_in()).map(move |j| i.set(i.get() * self.mod_in() + j))) .collect() } } @@ -627,7 +637,7 @@ macro_rules! dispatch { }; } -impl UnboundedMap for Elementary { +impl Map for Elementary { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn mod_in(&self) -> usize} @@ -682,100 +692,47 @@ impl From for Elementary { Self::UniformPoints(uniform_points) } } - -pub trait PushElementary { - fn push_elementary(&mut self, map: &Elementary); - fn clone_and_push_elementary(&self, map: &Elementary) -> Self - where - Self: Clone, - { - let mut cloned = self.clone(); - cloned.push_elementary(map); - cloned - } -} - -impl PushElementary for Vec { - fn push_elementary(&mut self, map: &Elementary) { - self.push(map.clone()); - } -} - #[cfg(test)] mod tests { use super::*; use approx::assert_abs_diff_eq; use std::iter; use Simplex::*; - - macro_rules! assert_eq_apply { - ($item:expr, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ - use std::borrow::Borrow; - let item = $item.borrow(); - let incoords = $incoords; - let outcoords = $outcoords; - assert_eq!(incoords.len(), outcoords.len()); - let stride; - let mut work: Vec<_>; - if incoords.len() == 0 { - stride = item.dim_out(); - work = Vec::with_capacity(0); - } else { - stride = outcoords[0].len(); - work = iter::repeat(-1.0).take(outcoords.len() * stride).collect(); - for (work, incoord) in iter::zip(work.chunks_mut(stride), incoords.iter()) { - work[..incoord.len()].copy_from_slice(incoord); - } - } - assert_eq!(item.apply_inplace($inidx, &mut work, stride, 0), $outidx); - for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { - assert_abs_diff_eq!(actual[..], desired[..]); - } - }}; - ($item:expr, $inidx:expr, $outidx:expr) => {{ - use std::borrow::Borrow; - let item = $item.borrow(); - let mut work = Vec::with_capacity(0); - assert_eq!( - item.apply_inplace($inidx, &mut work, item.dim_out(), 0), - $outidx - ); - }}; - } + use crate::assert_map_apply; #[test] fn apply_transpose() { let item = Elementary::new_transpose(3, 2); - assert_eq_apply!(item, 0, 0); - assert_eq_apply!(item, 1, 3); - assert_eq_apply!(item, 2, 1); - assert_eq_apply!(item, 3, 4); - assert_eq_apply!(item, 4, 2); - assert_eq_apply!(item, 5, 5); - assert_eq_apply!(item, 6, 6); - assert_eq_apply!(item, 7, 9); + assert_map_apply!(item, 0, 0); + assert_map_apply!(item, 1, 3); + assert_map_apply!(item, 2, 1); + assert_map_apply!(item, 3, 4); + assert_map_apply!(item, 4, 2); + assert_map_apply!(item, 5, 5); + assert_map_apply!(item, 6, 6); + assert_map_apply!(item, 7, 9); } #[test] fn apply_take() { let item = Elementary::new_take(vec![4, 1, 2], 5); - assert_eq_apply!(item, 0, 4); - assert_eq_apply!(item, 1, 1); - assert_eq_apply!(item, 2, 2); - assert_eq_apply!(item, 3, 9); - assert_eq_apply!(item, 4, 6); - assert_eq_apply!(item, 5, 7); + assert_map_apply!(item, 0, 4); + assert_map_apply!(item, 1, 1); + assert_map_apply!(item, 2, 2); + assert_map_apply!(item, 3, 9); + assert_map_apply!(item, 4, 6); + assert_map_apply!(item, 5, 7); } #[test] fn apply_children_line() { let item = Elementary::new_children(Line); - assert_eq_apply!(item, 0, [[0.0], [1.0]], 0, [[0.0], [0.5]]); - assert_eq_apply!(item, 1, [[0.0], [1.0]], 0, [[0.5], [1.0]]); - assert_eq_apply!(item, 2, [[0.0], [1.0]], 1, [[0.0], [0.5]]); + assert_map_apply!(item, 0, [[0.0], [1.0]], 0, [[0.0], [0.5]]); + assert_map_apply!(item, 1, [[0.0], [1.0]], 0, [[0.5], [1.0]]); + assert_map_apply!(item, 2, [[0.0], [1.0]], 1, [[0.0], [0.5]]); let item = item.with_offset(1); - assert_eq_apply!( + assert_map_apply!( item, 3, [[0.2, 0.0], [0.3, 1.0]], @@ -787,24 +744,24 @@ mod tests { #[test] fn apply_edges_line() { let item = Elementary::new_edges(Line); - assert_eq_apply!(item, 0, [[]], 0, [[1.0]]); - assert_eq_apply!(item, 1, [[]], 0, [[0.0]]); - assert_eq_apply!(item, 2, [[]], 1, [[1.0]]); + assert_map_apply!(item, 0, [[]], 0, [[1.0]]); + assert_map_apply!(item, 1, [[]], 0, [[0.0]]); + assert_map_apply!(item, 2, [[]], 1, [[1.0]]); let item = item.with_offset(1); - assert_eq_apply!(item, 0, [[0.2]], 0, [[0.2, 1.0]]); + assert_map_apply!(item, 0, [[0.2]], 0, [[0.2, 1.0]]); } #[test] fn apply_uniform_points() { let item = Elementary::new_uniform_points(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); - assert_eq_apply!(item, 0, [[]], 0, [[1.0, 2.0]]); - assert_eq_apply!(item, 1, [[]], 0, [[3.0, 4.0]]); - assert_eq_apply!(item, 2, [[]], 0, [[5.0, 6.0]]); - assert_eq_apply!(item, 3, [[]], 1, [[1.0, 2.0]]); + assert_map_apply!(item, 0, [[]], 0, [[1.0, 2.0]]); + assert_map_apply!(item, 1, [[]], 0, [[3.0, 4.0]]); + assert_map_apply!(item, 2, [[]], 0, [[5.0, 6.0]]); + assert_map_apply!(item, 3, [[]], 1, [[1.0, 2.0]]); let item = item.with_offset(1); - assert_eq_apply!(item, 0, [[7.0]], 0, [[7.0, 1.0, 2.0]]); + assert_map_apply!(item, 0, [[7.0]], 0, [[7.0, 1.0, 2.0]]); } macro_rules! assert_unapply { diff --git a/src/lib.rs b/src/lib.rs index 572a6ace9..ad82ccdc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,75 +1,16 @@ pub mod elementary; pub mod finite_f64; pub mod ops; -pub mod relative; +//pub mod relative; pub mod simplex; -pub mod tesselation; -pub mod topology; +//pub mod tesselation; +//pub mod topology; -pub trait BoundedMap { - fn len_out(&self) -> usize; - fn len_in(&self) -> usize; - fn dim_out(&self) -> usize { - self.dim_in() + self.delta_dim() - } - fn dim_in(&self) -> usize; - fn delta_dim(&self) -> usize; - fn apply_inplace_unchecked( - &self, - index: usize, - coordinates: &mut [f64], - stride: usize, - offset: usize, - ) -> usize; - fn apply_inplace( - &self, - index: usize, - coordinates: &mut [f64], - stride: usize, - offset: usize, - ) -> Option { - if index < self.len_in() && offset + self.dim_out() <= stride { - Some(self.apply_inplace_unchecked(index, coordinates, stride, offset)) - } else { - None - } - } - fn apply_index_unchecked(&self, index: usize) -> usize; - fn apply_index(&self, index: usize) -> Option { - if index < self.len_in() { - Some(self.apply_index_unchecked(index)) - } else { - None - } - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - for index in indices.iter_mut() { - *index = self.apply_index_unchecked(*index); - } - } - fn apply_indices(&self, indices: &[usize]) -> Option> { - if indices.iter().all(|index| *index < self.len_in()) { - let mut indices = indices.to_vec(); - self.apply_indices_inplace_unchecked(&mut indices); - Some(indices) - } else { - None - } - } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec; - fn unapply_indices(&self, indices: &[T]) -> Option> { - if indices.iter().all(|index| index.last() < self.len_out()) { - Some(self.unapply_indices_unchecked(indices)) - } else { - None - } - } - fn is_identity(&self) -> bool; -} +use num::Integer as _; -pub trait UnboundedMap { +pub trait Map { // Minimum dimension of the input coordinate. If the dimension of the input - // coordinate of [UnboundedMap::apply()] is larger than the minimum, then + // coordinate of [Map::apply_inplace()] is larger than the minimum, then // the map of the surplus is the identity map. fn dim_in(&self) -> usize; // Minimum dimension of the output coordinate. @@ -83,6 +24,14 @@ pub trait UnboundedMap { fn mod_in(&self) -> usize; // Modulus if the output index. fn mod_out(&self) -> usize; + fn apply_mod_out_to_in(&self, n: usize) -> Option { + let (i, rem) = n.div_rem(&self.mod_out()); + (rem == 0).then(|| i * self.mod_in()) + } + fn apply_mod_in_to_out(&self, n: usize) -> Option { + let (i, rem) = n.div_rem(&self.mod_in()); + (rem == 0).then(|| i * self.mod_out()) + } fn apply_inplace( &self, index: usize, @@ -107,17 +56,53 @@ pub trait AddOffset { } pub trait UnapplyIndicesData: Clone + std::fmt::Debug { - fn last(&self) -> usize; - fn push(&self, index: usize) -> Self; + fn get(&self) -> usize; + fn set(&self, index: usize) -> Self; } impl UnapplyIndicesData for usize { #[inline] - fn last(&self) -> usize { + fn get(&self) -> usize { *self } #[inline] - fn push(&self, index: usize) -> Self { + fn set(&self, index: usize) -> Self { index } } + +#[macro_export] +macro_rules! assert_map_apply { + ($item:expr, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ + use std::borrow::Borrow; + let item = $item.borrow(); + let incoords = $incoords; + let outcoords = $outcoords; + assert_eq!(incoords.len(), outcoords.len()); + let stride; + let mut work: Vec<_>; + if incoords.len() == 0 { + stride = item.dim_out(); + work = Vec::with_capacity(0); + } else { + stride = outcoords[0].len(); + work = iter::repeat(-1.0).take(outcoords.len() * stride).collect(); + for (work, incoord) in iter::zip(work.chunks_mut(stride), incoords.iter()) { + work[..incoord.len()].copy_from_slice(incoord); + } + } + assert_eq!(item.apply_inplace($inidx, &mut work, stride, 0), $outidx); + for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { + assert_abs_diff_eq!(actual[..], desired[..]); + } + }}; + ($item:expr, $inidx:expr, $outidx:expr) => {{ + use std::borrow::Borrow; + let item = $item.borrow(); + let mut work = Vec::with_capacity(0); + assert_eq!( + item.apply_inplace($inidx, &mut work, item.dim_out(), 0), + $outidx + ); + }}; +} diff --git a/src/ops.rs b/src/ops.rs index 7655e7f2e..2b90b24f8 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,210 +1,99 @@ -use crate::elementary::{Elementary, PushElementary, Transpose}; -use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; +use crate::{Map, UnapplyIndicesData}; use num::Integer as _; -use std::ops::{Deref, DerefMut}; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum WithBoundsError { - DimensionTooSmall, - LengthNotAMultipleOfRepetition, -} - -impl std::error::Error for WithBoundsError {} - -impl std::fmt::Display for WithBoundsError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::DimensionTooSmall => write!(f, "The dimension of the sized map is smaller than the minimum dimension of the unsized map."), - Self::LengthNotAMultipleOfRepetition => write!(f, "The length of the sized map is not a multiple of the repetition length of the unsized map."), - } - } -} +use std::ops::Deref; #[derive(Debug, Clone, PartialEq)] -pub struct WithBounds { +pub struct Offset { map: M, - delta_dim: usize, - dim_in: usize, - len_out: usize, - len_in: usize, + offset: usize, } -impl WithBounds { - pub fn new(map: M, dim_out: usize, len_out: usize) -> Result { - if dim_out < map.dim_out() { - Err(WithBoundsError::DimensionTooSmall) - } else if len_out % map.mod_out() != 0 { - Err(WithBoundsError::LengthNotAMultipleOfRepetition) +impl Map for Offset { + #[inline] + fn dim_in(&self) -> usize { + if self.map.dim_out() > 0 { + self.map.dim_in() + self.offset } else { - Ok(Self::new_unchecked(map, dim_out, len_out)) - } - } - pub(crate) fn new_unchecked(map: M, dim_out: usize, len_out: usize) -> Self { - let delta_dim = map.delta_dim(); - let dim_in = dim_out - delta_dim; - let len_in = len_out / map.mod_out() * map.mod_in(); - Self { - map, - delta_dim, - dim_in, - len_out, - len_in, + 0 } } - pub fn get_unbounded(&self) -> &M { - &self.map - } - pub fn into_unbounded(self) -> M { - self.map - } -} - -impl BoundedMap for WithBounds { - fn len_in(&self) -> usize { - self.len_in - } - fn len_out(&self) -> usize { - self.len_out + #[inline] + fn delta_dim(&self) -> usize { + self.map.delta_dim() } - fn dim_in(&self) -> usize { - self.dim_in + #[inline] + fn mod_in(&self) -> usize { + self.map.mod_in() } - fn delta_dim(&self) -> usize { - self.delta_dim + #[inline] + fn mod_out(&self) -> usize { + self.map.mod_out() } - fn apply_inplace_unchecked( + #[inline] + fn apply_inplace( &self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize, ) -> usize { - self.map.apply_inplace(index, coordinates, stride, offset) + self.map + .apply_inplace(index, coordinates, stride, offset + self.offset) } - fn apply_index_unchecked(&self, index: usize) -> usize { + #[inline] + fn apply_index(&self, index: usize) -> usize { self.map.apply_index(index) } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.map.apply_indices_inplace(indices) + #[inline] + fn apply_indices_inplace(&self, indices: &mut [usize]) { + self.map.apply_indices_inplace(indices); } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + #[inline] + fn unapply_indices(&self, indices: &[T]) -> Vec { self.map.unapply_indices(indices) } + #[inline] fn is_identity(&self) -> bool { self.map.is_identity() } } -impl AddOffset for WithBounds { - fn add_offset(&mut self, offset: usize) { - self.map.add_offset(offset); - self.dim_in += offset; - } -} - -impl PushElementary for WithBounds { - fn push_elementary(&mut self, item: &Elementary) { - // TODO: return an error if we push something that causes `dim_in` to drop below zero - // or with incompatible length. - assert!(self.dim_in >= item.delta_dim()); - self.delta_dim += item.delta_dim(); - self.dim_in -= item.delta_dim(); - if item.mod_out() > 0 { - assert_eq!(self.len_in % item.mod_out(), 0); - self.len_in = self.len_in / item.mod_out() * item.mod_in(); - } else { - self.len_in = 0; - } - self.map.push_elementary(item); - } +#[inline] +fn comp_dim_out_in(map: &M, dim_out: usize, dim_in: usize) -> (usize, usize) { + let n = map.dim_in().checked_sub(dim_out).unwrap_or(0); + (dim_out + map.delta_dim() + n, dim_in + n) } -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct ComposeCodomainDomainMismatch; - -impl std::error::Error for ComposeCodomainDomainMismatch {} - -impl std::fmt::Display for ComposeCodomainDomainMismatch { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "The codomain of the first map doesn't match the domain of the second map," - ) - } +#[inline] +fn comp_mod_out_in(map: &M, mod_out: usize, mod_in: usize) -> (usize, usize) { + let n = mod_out.lcm(&map.mod_in()); + (n / map.mod_in() * map.mod_out(), mod_in * n / mod_out) } #[derive(Debug, Clone, PartialEq)] -pub struct Composition { +pub struct BinaryComposition { inner: Inner, outer: Outer, } -impl Composition { - pub fn new(inner: Inner, outer: Outer) -> Result { - if inner.len_out() == outer.len_in() && inner.dim_out() == outer.dim_in() { - Ok(Self { inner, outer }) - } else { - Err(ComposeCodomainDomainMismatch) - } - } -} - -impl BoundedMap for Composition { - fn len_in(&self) -> usize { - self.inner.len_in() - } - fn len_out(&self) -> usize { - self.outer.len_out() - } +impl Map for BinaryComposition { + #[inline] fn dim_in(&self) -> usize { - self.inner.dim_in() + comp_dim_out_in(&self.outer, self.inner.dim_out(), self.inner.dim_in()).1 } + #[inline] fn delta_dim(&self) -> usize { self.inner.delta_dim() + self.outer.delta_dim() } - fn apply_inplace_unchecked( - &self, - index: usize, - coordinates: &mut [f64], - stride: usize, - offset: usize, - ) -> usize { - let index = self - .inner - .apply_inplace_unchecked(index, coordinates, stride, offset); - self.outer - .apply_inplace_unchecked(index, coordinates, stride, offset) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - let index = self.inner.apply_index_unchecked(index); - self.outer.apply_index_unchecked(index) - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.inner.apply_indices_inplace_unchecked(indices); - self.outer.apply_indices_inplace_unchecked(indices); - } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - let indices = self.outer.unapply_indices_unchecked(indices); - self.inner.unapply_indices_unchecked(&indices) - } - fn is_identity(&self) -> bool { - self.inner.is_identity() && self.outer.is_identity() - } -} - -impl UnboundedMap for Composition { + #[inline] fn mod_in(&self) -> usize { - update_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).1 + comp_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).0 } + #[inline] fn mod_out(&self) -> usize { - update_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).0 - } - fn dim_in(&self) -> usize { - update_dim_out_in(&self.outer, self.inner.dim_out(), self.inner.dim_in()).0 - } - fn delta_dim(&self) -> usize { - self.inner.delta_dim() + self.outer.delta_dim() + comp_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).1 } + #[inline] fn apply_inplace( &self, index: usize, @@ -215,304 +104,841 @@ impl UnboundedMap for Composition usize { - let index = self.inner.apply_index(index); - self.outer.apply_index(index) + self.outer.apply_index(self.inner.apply_index(index)) } + #[inline] fn apply_indices_inplace(&self, indices: &mut [usize]) { self.inner.apply_indices_inplace(indices); self.outer.apply_indices_inplace(indices); } + #[inline] fn unapply_indices(&self, indices: &[T]) -> Vec { - let indices = self.outer.unapply_indices(indices); - self.inner.unapply_indices(&indices) + self.inner + .unapply_indices(&self.outer.unapply_indices(indices)) } + #[inline] fn is_identity(&self) -> bool { self.inner.is_identity() && self.outer.is_identity() } } -impl AddOffset for Composition { - fn add_offset(&mut self, offset: usize) { - self.inner.add_offset(offset); - self.outer.add_offset(offset); - } -} - -pub trait Compose: BoundedMap + Sized { - fn compose( - self, - rhs: Rhs, - ) -> Result, ComposeCodomainDomainMismatch> { - Composition::new(self, rhs) - } -} - -impl Compose for M {} - -#[derive(Debug, Clone, PartialEq)] -pub struct Concatenation(Vec); +pub struct UniformComposition(Array) +where + M: Map, + Array: Deref; -impl Concatenation { - pub fn new(items: Vec) -> Self { - // TODO: Return `Result`. - let first = items.first().unwrap(); - let dim_in = first.dim_in(); - let delta_dim = first.delta_dim(); - let len_out = first.len_out(); - for item in items.iter() { - assert_eq!(item.dim_in(), dim_in); - assert_eq!(item.delta_dim(), delta_dim); - assert_eq!(item.len_out(), len_out); - } - Self(items) - } - fn resolve_item_unchecked(&self, mut index: usize) -> (&Item, usize) { - for item in self.0.iter() { - if index < item.len_in() { - return (item, index); - } - index -= item.len_in(); - } - panic!("index out of range"); - } - pub fn iter(&self) -> impl Iterator { +impl UniformComposition +where + M: Map, + Array: Deref, +{ + pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, M> { self.0.iter() } - pub fn into_vec(self) -> Vec { - self.0 - } } -impl BoundedMap for Concatenation { +impl Map for UniformComposition +where + M: Map, + Array: Deref, +{ + #[inline] fn dim_in(&self) -> usize { - self.0.first().unwrap().dim_in() + self.iter() + .fold((0, 0), |(o, i), map| comp_dim_out_in(map, o, i)) + .1 } + #[inline] fn delta_dim(&self) -> usize { - self.0.first().unwrap().delta_dim() + self.iter().map(|map| map.delta_dim()).sum() } - fn len_out(&self) -> usize { - self.0.first().unwrap().len_out() + #[inline] + fn mod_in(&self) -> usize { + self.iter() + .fold((1, 1), |(o, i), map| comp_mod_out_in(map, o, i)) + .1 } - fn len_in(&self) -> usize { - self.iter().map(|item| item.len_in()).sum() + #[inline] + fn mod_out(&self) -> usize { + self.iter() + .fold((1, 1), |(o, i), map| comp_mod_out_in(map, o, i)) + .0 } - fn apply_inplace_unchecked( + #[inline] + fn apply_inplace( &self, index: usize, - coordinates: &mut [f64], + coords: &mut [f64], stride: usize, offset: usize, ) -> usize { - let (item, index) = self.resolve_item_unchecked(index); - item.apply_inplace_unchecked(index, coordinates, stride, offset) + self.iter().fold(index, |index, map| { + map.apply_inplace(index, coords, stride, offset) + }) } - fn apply_index_unchecked(&self, index: usize) -> usize { - let (item, index) = self.resolve_item_unchecked(index); - item.apply_index_unchecked(index) + #[inline] + fn apply_index(&self, index: usize) -> usize { + self.iter().fold(index, |index, map| map.apply_index(index)) } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - let mut result = Vec::new(); - let mut offset = 0; - for item in &self.0 { - result.extend( - item.unapply_indices_unchecked(indices) - .into_iter() - .map(|i| i.push(i.last() + offset)), - ); - offset += item.len_in(); + #[inline] + fn apply_indices_inplace(&self, indices: &mut [usize]) { + self.iter() + .for_each(|map| map.apply_indices_inplace(indices)); + } + #[inline] + fn unapply_indices(&self, indices: &[T]) -> Vec { + let mut iter = self.iter().rev(); + if let Some(map) = iter.next() { + iter.fold(map.unapply_indices(indices), |indices, map| { + map.unapply_indices(&indices) + }) + } else { + Vec::new() } - result } + #[inline] fn is_identity(&self) -> bool { - false + self.iter().all(|map| map.is_identity()) } } -impl AddOffset for Concatenation { - fn add_offset(&mut self, offset: usize) { - for item in &mut self.0 { - item.add_offset(offset); +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ConcatError { + ModulusNotAMultiple, + DeltaDimsDiffer, +} + +impl std::error::Error for ConcatError {} + +impl std::fmt::Display for ConcatError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::ModulusNotAMultiple => write!( + f, + "The given output modulus is not a multiple of the output moduli of the given maps.", + ), + Self::DeltaDimsDiffer => write!( + f, "Cannot concatenate maps with different delta dimensions."), } } } -impl PushElementary for Concatenation -where - M: BoundedMap + PushElementary, -{ - fn push_elementary(&mut self, map: &Elementary) { - match map { - Elementary::Children(_) | Elementary::Edges(_) => { - for item in &mut self.0 { - item.push_elementary(map); - } - } - Elementary::Take(take) => { - let mut offset = 0; - let indices = take.get_indices(); - let mut indices = indices.iter().cloned().peekable(); - for item in &mut self.0 { - let len = item.len_in(); - let mut item_indices = Vec::new(); - while let Some(index) = indices.next_if(|&i| i < offset + len) { - if index < offset { - unimplemented! {"take of concatenation with unordered indices"}; - } - item_indices.push(index - offset); - } - item.push_elementary(&Elementary::new_take(item_indices, len)); - offset += len; - } - } - _ => unimplemented! {}, +//fold_map! {BinaryConcat, UniformConcat; +// type Item = (M, usize); +// dim_in +// |(map, _)| map.dim_in() +// |dim_in, (map, _)| std::cmp::max(dim_in, map.dim_in()) +// |dim_in| dim_in +// delta_dim +// |(map, _)| map.delta_dim() +// |delta_dim, (map, _)| delta_dim + map.delta_dim() +// |delta_dim| delta_dim +// mod_out_in +// |(map, len)| (map.apply_mod_in_to_out(len).unwrap(), len) +// |(mod_out, mod_in), (_, len)| (mod_out, mod_in + len) +// |state| state +// apply +// |apply, index, (map, len)| { +// let (i, j) = index.div_rem(&self.mod_in()); +// if j < len { +// (apply(map, j + i * len), i, j, len) +// } else { +// (index, i, j, len) +// } +// } +// |apply, (index, i, j, k), (map, len)| { +// if k <= j && j < k + len { +// (apply(map, j + i * len), i, j, k + len) +// } else { +// (index, i, j, k + len) +// } +// } +// |(index, i, j, k)| done +// unapply +// |indices, (map, len)| ( +// map.unapply_indices(indices) +// .into_iter() +// .map(|i| i.set(i.get() / len * self.mod_in() + i.get() % len)) +// .collect::>(), +// len +// ) +// |(mut indices, k), (map, len)| ( +// indices.extend( +// map.unapply_indices(indices) +// .into_iter() +// .map(|i| i.set(i.get() / len * self.mod_in() + i.get() % len + k))), +// k + len +// ) +// |(indices, k)| indices +//} + +pub struct BinaryConcat { + map1: M1, + map2: M2, + mod_out: usize, + mod_in1: usize, + mod_in2: usize, +} + +impl BinaryConcat { + pub fn new(map1: M1, map2: M2, mod_out: usize) -> Result { + let mod_in1 = map1 + .apply_mod_out_to_in(mod_out) + .ok_or(ConcatError::ModulusNotAMultiple)?; + let mod_in2 = map2 + .apply_mod_out_to_in(mod_out) + .ok_or(ConcatError::ModulusNotAMultiple)?; + if map1.delta_dim() != map2.delta_dim() { + return Err(ConcatError::DeltaDimsDiffer); } + Ok(Self { + map1, + map2, + mod_out, + mod_in1, + mod_in2, + }) } } -struct Product(M0, M1); - -impl BoundedMap for Product { +impl Map for BinaryConcat { + #[inline] fn dim_in(&self) -> usize { - self.0.dim_in() + self.1.dim_in() + std::cmp::max(self.map1.dim_in(), self.map2.dim_in()) } + #[inline] fn delta_dim(&self) -> usize { - self.0.delta_dim() + self.1.delta_dim() + self.map1.delta_dim() } - fn len_out(&self) -> usize { - self.0.len_out() * self.1.len_out() + #[inline] + fn mod_in(&self) -> usize { + self.mod_in1 + self.mod_in2 } - fn len_in(&self) -> usize { - self.0.len_in() * self.1.len_in() + #[inline] + fn mod_out(&self) -> usize { + self.mod_out } - fn apply_inplace_unchecked( + #[inline] + fn apply_inplace( &self, index: usize, coords: &mut [f64], stride: usize, offset: usize, ) -> usize { - let index = self - .1 - .apply_inplace_unchecked(index, coords, stride, offset + self.0.dim_in()); - let index = Transpose::new(self.0.len_in(), self.1.len_in()).apply_index(index); - let index = self - .0 - .apply_inplace_unchecked(index, coords, stride, offset); - Transpose::new(self.1.len_in(), self.0.len_in()).apply_index(index) - } - fn apply_index_unchecked(&self, index: usize) -> usize { - let index = self.1.apply_index_unchecked(index); - let index = Transpose::new(self.0.len_in(), self.1.len_in()).apply_index(index); - let index = self.0.apply_index_unchecked(index); - Transpose::new(self.1.len_in(), self.0.len_in()).apply_index(index) - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.1.apply_indices_inplace_unchecked(indices); - Transpose::new(self.0.len_in(), self.1.len_in()).apply_indices_inplace(indices); - self.0.apply_indices_inplace_unchecked(indices); - Transpose::new(self.1.len_in(), self.0.len_in()).apply_indices_inplace(indices); - } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - let indices = - Transpose::new(self.1.len_in(), self.0.len_in()).unapply_indices(indices); - let indices = self.0.unapply_indices_unchecked(&indices); - let indices = - Transpose::new(self.0.len_in(), self.1.len_in()).unapply_indices(&indices); - self.1.unapply_indices_unchecked(&indices) + let (i, j) = index.div_rem(&self.mod_in()); + if j < self.mod_in1 { + self.map1 + .apply_inplace(j + i * self.mod_in1, coords, stride, offset) + } else { + self.map2 + .apply_inplace(j - self.mod_in1 + i * self.mod_in1, coords, stride, offset) + } + } + #[inline] + fn apply_index(&self, index: usize) -> usize { + let (i, j) = index.div_rem(&self.mod_in()); + if let Some(j) = j.checked_sub(self.mod_in1) { + self.map2.apply_index(j + i * self.mod_in2) + } else { + self.map1.apply_index(j + i * self.mod_in1) + } } + #[inline] + fn unapply_indices(&self, indices: &[T]) -> Vec { + let mut result: Vec<_> = self + .map1 + .unapply_indices(indices) + .into_iter() + .map(|i| i.set(i.get() / self.mod_in1 * self.mod_in() + i.get() % self.mod_in1)) + .collect(); + result.extend(self.map2.unapply_indices(indices).into_iter().map(|i| { + i.set(i.get() / self.mod_in2 * self.mod_in() + self.mod_in1 + i.get() % self.mod_in2) + })); + result + } + #[inline] fn is_identity(&self) -> bool { - self.0.is_identity() && self.1.is_identity() + self.map1.is_identity() && self.map2.is_identity() } } -#[inline] -fn update_dim_out_in(map: &M, dim_out: usize, dim_in: usize) -> (usize, usize) { - if let Some(n) = map.dim_in().checked_sub(dim_out) { - (map.dim_out(), dim_in + n) - } else { - (dim_out, dim_in) - } +struct UniformConcat +where + M: Map, + Array: Deref, +{ + maps: Array, + mod_out: usize, } -#[inline] -fn update_mod_out_in(map: &M, mod_out: usize, mod_in: usize) -> (usize, usize) { - let n = mod_out.lcm(&map.mod_in()); - (n / map.mod_in() * map.mod_out(), mod_in * n / mod_out) -} +//impl UniformConcat { +//where +// M: Map, +// Array: Deref, +//{ +// pub fn new(maps: Array M2, mod_out: usize) -> Result { +// // TODO: assert unempty +// let mod_in1 = map1 +// .apply_mod_out_to_in(mod_out) +// .ok_or(ConcatError::ModulusNotAMultiple)?; +// let mod_in2 = map2 +// .apply_mod_out_to_in(mod_out) +// .ok_or(ConcatError::ModulusNotAMultiple)?; +// if map1.delta_dim() != map2.delta_dim() { +// return Err(ConcatError::DeltaDimsDiffer); +// } +// Ok(Self { +// map1, +// map2, +// mod_out, +// mod_in1, +// mod_in2, +// }) +// } +//} -/// Composition. -impl UnboundedMap for Array +impl Map for UniformConcat where - Item: UnboundedMap, - Array: Deref + DerefMut + std::fmt::Debug, + M: Map, + Array: Deref, { + #[inline] fn dim_in(&self) -> usize { - self.deref() - .iter() - .rev() - .fold((0, 0), |(dim_out, dim_in), item| { - update_dim_out_in(item, dim_out, dim_in) - }) - .1 + self.maps.iter().map(|(map, _)| map.dim_in()).max().unwrap() } + #[inline] fn delta_dim(&self) -> usize { - self.iter().map(|item| item.delta_dim()).sum() + self.maps.first().unwrap().0.delta_dim() } + #[inline] fn mod_in(&self) -> usize { - self.deref() - .iter() - .rev() - .fold((1, 1), |(mod_out, mod_in), item| { - update_mod_out_in(item, mod_out, mod_in) - }) - .1 + self.maps.iter().map(|(_, mod_in)| mod_in).sum() } + #[inline] fn mod_out(&self) -> usize { - self.deref() - .iter() - .rev() - .fold((1, 1), |(mod_out, mod_in), item| { - update_mod_out_in(item, mod_out, mod_in) - }) - .0 + self.mod_out } + #[inline] fn apply_inplace( &self, index: usize, - coordinates: &mut [f64], + coords: &mut [f64], stride: usize, offset: usize, ) -> usize { - self.iter().rev().fold(index, |index, item| { - item.apply_inplace(index, coordinates, stride, offset) - }) + let (i, mut j) = index.div_rem(&self.mod_in()); + for (map, mod_in) in self.maps.iter() { + if j < *mod_in { + return map.apply_inplace(j + i * mod_in, coords, stride, offset); + } + j -= mod_in; + } + unreachable! {} } + #[inline] fn apply_index(&self, index: usize) -> usize { - self.iter() - .rev() - .fold(index, |index, item| item.apply_index(index)) - } - fn apply_indices_inplace(&self, indices: &mut [usize]) { - for item in self.iter().rev() { - item.apply_indices_inplace(indices); + let (i, mut j) = index.div_rem(&self.mod_in()); + for (map, mod_in) in self.maps.iter() { + if j < *mod_in { + return map.apply_index(j + i * mod_in); + } + j -= mod_in; } + unreachable! {} } + #[inline] fn unapply_indices(&self, indices: &[T]) -> Vec { - self.iter().fold(indices.to_vec(), |indices, item| { - item.unapply_indices(&indices) - }) + let mut result = Vec::new(); + let mut offset = 0; + for (map, mod_in) in self.maps.iter() { + result.extend( + map.unapply_indices(indices) + .iter() + .map(|i| i.set(i.get() / mod_in * self.mod_in() + offset + i.get() % mod_in)), + ); + offset += mod_in; + } + result + } + #[inline] + fn is_identity(&self) -> bool { + self.maps.iter().all(|(map, _)| map.is_identity()) } } -impl AddOffset for Array -where - Item: AddOffset, - Array: Deref + DerefMut + std::fmt::Debug, -{ - fn add_offset(&mut self, offset: usize) { - for item in self.iter_mut() { - item.add_offset(offset); - } +#[cfg(test)] +mod tests { + use super::*; + use approx::assert_abs_diff_eq; + use crate::elementary::{Elementary, Identity}; + use crate::simplex::Simplex::*; + use std::iter; + use crate::assert_map_apply; + + #[test] + fn uniform_composition0() { + let map = UniformComposition(Vec::::new()); + assert_eq!(map.mod_in(), 1); + assert_eq!(map.mod_out(), 1); + assert_eq!(map.dim_in(), 0); + assert_eq!(map.delta_dim(), 0); + assert_map_apply!(map, 0, 0); + assert_map_apply!(map, 1, 1); + } + + #[test] + fn uniform_composition1() { + let map = UniformComposition(vec![Identity]); + assert_eq!(map.mod_in(), 1); + assert_eq!(map.mod_out(), 1); + assert_eq!(map.dim_in(), 0); + assert_eq!(map.delta_dim(), 0); + assert_map_apply!(map, 0, 0); + assert_map_apply!(map, 1, 1); + } + + #[test] + fn uniform_composition2() { + let map = UniformComposition(vec![ + Elementary::new_take(vec![1, 0], 3), + Elementary::new_transpose(4, 3), + ]); + assert_eq!(map.mod_in(), 8); + assert_eq!(map.mod_out(), 12); + assert_eq!(map.dim_in(), 0); + assert_eq!(map.delta_dim(), 0); + assert_map_apply!(map, 0, 4); + assert_map_apply!(map, 1, 0); + assert_map_apply!(map, 2, 5); + assert_map_apply!(map, 3, 1); + assert_map_apply!(map, 4, 6); + assert_map_apply!(map, 5, 2); + assert_map_apply!(map, 6, 7); + assert_map_apply!(map, 7, 3); + assert_map_apply!(map, 8, 16); + } + + #[test] + fn uniform_composition3() { + let map = UniformComposition(vec![ + Elementary::new_edges(Line), + Elementary::new_children(Line), + Elementary::new_children(Line), + ]); + assert_eq!(map.mod_in(), 8); + assert_eq!(map.mod_out(), 1); + assert_eq!(map.dim_in(), 0); + assert_eq!(map.delta_dim(), 1); + assert_map_apply!(map, 0, [[]], 0, [[0.25]]); + assert_map_apply!(map, 1, [[]], 0, [[0.00]]); + assert_map_apply!(map, 2, [[]], 0, [[0.50]]); + assert_map_apply!(map, 3, [[]], 0, [[0.25]]); + assert_map_apply!(map, 4, [[]], 0, [[0.75]]); + assert_map_apply!(map, 5, [[]], 0, [[0.50]]); + assert_map_apply!(map, 6, [[]], 0, [[1.00]]); + assert_map_apply!(map, 7, [[]], 0, [[0.75]]); + assert_map_apply!(map, 8, [[]], 1, [[0.25]]); + assert_eq!(map.unapply_indices(&[0]), vec![0, 1, 2, 3, 4, 5, 6, 7]); } } + +//#[derive(Debug, Clone, Copy, PartialEq)] +//pub struct ComposeCodomainDomainMismatch; +// +//impl std::error::Error for ComposeCodomainDomainMismatch {} +// +//impl std::fmt::Display for ComposeCodomainDomainMismatch { +// fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +// write!( +// f, +// "The codomain of the first map doesn't match the domain of the second map," +// ) +// } +//} +// +//#[derive(Debug, Clone, PartialEq)] +//pub struct Composition { +// inner: Inner, +// outer: Outer, +//} +// +//impl Composition { +// pub fn new(inner: Inner, outer: Outer) -> Result { +// if inner.len_out() == outer.len_in() && inner.dim_out() == outer.dim_in() { +// Ok(Self { inner, outer }) +// } else { +// Err(ComposeCodomainDomainMismatch) +// } +// } +//} +// +//impl BoundedMap for Composition { +// fn len_in(&self) -> usize { +// self.inner.len_in() +// } +// fn len_out(&self) -> usize { +// self.outer.len_out() +// } +// fn dim_in(&self) -> usize { +// self.inner.dim_in() +// } +// fn delta_dim(&self) -> usize { +// self.inner.delta_dim() + self.outer.delta_dim() +// } +// fn apply_inplace_unchecked( +// &self, +// index: usize, +// coordinates: &mut [f64], +// stride: usize, +// offset: usize, +// ) -> usize { +// let index = self +// .inner +// .apply_inplace_unchecked(index, coordinates, stride, offset); +// self.outer +// .apply_inplace_unchecked(index, coordinates, stride, offset) +// } +// fn apply_index_unchecked(&self, index: usize) -> usize { +// let index = self.inner.apply_index_unchecked(index); +// self.outer.apply_index_unchecked(index) +// } +// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { +// self.inner.apply_indices_inplace_unchecked(indices); +// self.outer.apply_indices_inplace_unchecked(indices); +// } +// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { +// let indices = self.outer.unapply_indices_unchecked(indices); +// self.inner.unapply_indices_unchecked(&indices) +// } +// fn is_identity(&self) -> bool { +// self.inner.is_identity() && self.outer.is_identity() +// } +//} +// +//impl UnboundedMap for Composition { +// fn mod_in(&self) -> usize { +// update_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).1 +// } +// fn mod_out(&self) -> usize { +// update_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).0 +// } +// fn dim_in(&self) -> usize { +// update_dim_out_in(&self.outer, self.inner.dim_out(), self.inner.dim_in()).0 +// } +// fn delta_dim(&self) -> usize { +// self.inner.delta_dim() + self.outer.delta_dim() +// } +// fn apply_inplace( +// &self, +// index: usize, +// coordinates: &mut [f64], +// stride: usize, +// offset: usize, +// ) -> usize { +// let index = self.inner.apply_inplace(index, coordinates, stride, offset); +// self.outer.apply_inplace(index, coordinates, stride, offset) +// } +// fn apply_index(&self, index: usize) -> usize { +// let index = self.inner.apply_index(index); +// self.outer.apply_index(index) +// } +// fn apply_indices_inplace(&self, indices: &mut [usize]) { +// self.inner.apply_indices_inplace(indices); +// self.outer.apply_indices_inplace(indices); +// } +// fn unapply_indices(&self, indices: &[T]) -> Vec { +// let indices = self.outer.unapply_indices(indices); +// self.inner.unapply_indices(&indices) +// } +// fn is_identity(&self) -> bool { +// self.inner.is_identity() && self.outer.is_identity() +// } +//} +// +//impl AddOffset for Composition { +// fn add_offset(&mut self, offset: usize) { +// self.inner.add_offset(offset); +// self.outer.add_offset(offset); +// } +//} +// +//pub trait Compose: BoundedMap + Sized { +// fn compose( +// self, +// rhs: Rhs, +// ) -> Result, ComposeCodomainDomainMismatch> { +// Composition::new(self, rhs) +// } +//} +// +//impl Compose for M {} +// +//#[derive(Debug, Clone, PartialEq)] +//pub struct Concat(Vec); +// +//impl Concat { +// pub fn new(items: Vec) -> Self { +// // TODO: Return `Result`. +// let first = items.first().unwrap(); +// let dim_in = first.dim_in(); +// let delta_dim = first.delta_dim(); +// let len_out = first.len_out(); +// for item in items.iter() { +// assert_eq!(item.dim_in(), dim_in); +// assert_eq!(item.delta_dim(), delta_dim); +// assert_eq!(item.len_out(), len_out); +// } +// Self(items) +// } +// fn resolve_item_unchecked(&self, mut index: usize) -> (&Item, usize) { +// for item in self.0.iter() { +// if index < item.len_in() { +// return (item, index); +// } +// index -= item.len_in(); +// } +// panic!("index out of range"); +// } +// pub fn iter(&self) -> impl Iterator { +// self.0.iter() +// } +// pub fn into_vec(self) -> Vec { +// self.0 +// } +//} +// +//impl BoundedMap for Concat { +// fn dim_in(&self) -> usize { +// self.0.first().unwrap().dim_in() +// } +// fn delta_dim(&self) -> usize { +// self.0.first().unwrap().delta_dim() +// } +// fn len_out(&self) -> usize { +// self.0.first().unwrap().len_out() +// } +// fn len_in(&self) -> usize { +// self.iter().map(|item| item.len_in()).sum() +// } +// fn apply_inplace_unchecked( +// &self, +// index: usize, +// coordinates: &mut [f64], +// stride: usize, +// offset: usize, +// ) -> usize { +// let (item, index) = self.resolve_item_unchecked(index); +// item.apply_inplace_unchecked(index, coordinates, stride, offset) +// } +// fn apply_index_unchecked(&self, index: usize) -> usize { +// let (item, index) = self.resolve_item_unchecked(index); +// item.apply_index_unchecked(index) +// } +// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { +// let mut result = Vec::new(); +// let mut offset = 0; +// for item in &self.0 { +// result.extend( +// item.unapply_indices_unchecked(indices) +// .into_iter() +// .map(|i| i.set(i.get() + offset)), +// ); +// offset += item.len_in(); +// } +// result +// } +// fn is_identity(&self) -> bool { +// false +// } +//} +// +//impl AddOffset for Concat { +// fn add_offset(&mut self, offset: usize) { +// for item in &mut self.0 { +// item.add_offset(offset); +// } +// } +//} +// +//impl PushElementary for Concat +//where +// M: BoundedMap + PushElementary, +//{ +// fn push_elementary(&mut self, map: &Elementary) { +// match map { +// Elementary::Children(_) | Elementary::Edges(_) => { +// for item in &mut self.0 { +// item.push_elementary(map); +// } +// } +// Elementary::Take(take) => { +// let mut offset = 0; +// let indices = take.get_indices(); +// let mut indices = indices.iter().cloned().peekable(); +// for item in &mut self.0 { +// let len = item.len_in(); +// let mut item_indices = Vec::new(); +// while let Some(index) = indices.next_if(|&i| i < offset + len) { +// if index < offset { +// unimplemented! {"take of concatenation with unordered indices"}; +// } +// item_indices.push(index - offset); +// } +// item.push_elementary(&Elementary::new_take(item_indices, len)); +// offset += len; +// } +// } +// _ => unimplemented! {}, +// } +// } +//} +// +//struct Product(M0, M1); +// +//impl BoundedMap for Product { +// fn dim_in(&self) -> usize { +// self.0.dim_in() + self.1.dim_in() +// } +// fn delta_dim(&self) -> usize { +// self.0.delta_dim() + self.1.delta_dim() +// } +// fn len_out(&self) -> usize { +// self.0.len_out() * self.1.len_out() +// } +// fn len_in(&self) -> usize { +// self.0.len_in() * self.1.len_in() +// } +// fn apply_inplace_unchecked( +// &self, +// index: usize, +// coords: &mut [f64], +// stride: usize, +// offset: usize, +// ) -> usize { +// let index = self +// .1 +// .apply_inplace_unchecked(index, coords, stride, offset + self.0.dim_in()); +// let index = Transpose::new(self.0.len_in(), self.1.len_in()).apply_index(index); +// let index = self +// .0 +// .apply_inplace_unchecked(index, coords, stride, offset); +// Transpose::new(self.1.len_in(), self.0.len_in()).apply_index(index) +// } +// fn apply_index_unchecked(&self, index: usize) -> usize { +// let index = self.1.apply_index_unchecked(index); +// let index = Transpose::new(self.0.len_in(), self.1.len_in()).apply_index(index); +// let index = self.0.apply_index_unchecked(index); +// Transpose::new(self.1.len_in(), self.0.len_in()).apply_index(index) +// } +// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { +// self.1.apply_indices_inplace_unchecked(indices); +// Transpose::new(self.0.len_in(), self.1.len_in()).apply_indices_inplace(indices); +// self.0.apply_indices_inplace_unchecked(indices); +// Transpose::new(self.1.len_in(), self.0.len_in()).apply_indices_inplace(indices); +// } +// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { +// let indices = +// Transpose::new(self.1.len_in(), self.0.len_in()).unapply_indices(indices); +// let indices = self.0.unapply_indices_unchecked(&indices); +// let indices = +// Transpose::new(self.0.len_in(), self.1.len_in()).unapply_indices(&indices); +// self.1.unapply_indices_unchecked(&indices) +// } +// fn is_identity(&self) -> bool { +// self.0.is_identity() && self.1.is_identity() +// } +//} +// +//#[inline] +//fn update_dim_out_in(map: &M, dim_out: usize, dim_in: usize) -> (usize, usize) { +// if let Some(n) = map.dim_in().checked_sub(dim_out) { +// (map.dim_out(), dim_in + n) +// } else { +// (dim_out, dim_in) +// } +//} +// +//#[inline] +//fn update_mod_out_in(map: &M, mod_out: usize, mod_in: usize) -> (usize, usize) { +// let n = mod_out.lcm(&map.mod_in()); +// (n / map.mod_in() * map.mod_out(), mod_in * n / mod_out) +//} +// +///// Composition. +//impl UnboundedMap for Array +//where +// Item: UnboundedMap, +// Array: Deref + DerefMut + std::fmt::Debug, +//{ +// fn dim_in(&self) -> usize { +// self.deref() +// .iter() +// .rev() +// .fold((0, 0), |(dim_out, dim_in), item| { +// update_dim_out_in(item, dim_out, dim_in) +// }) +// .1 +// } +// fn delta_dim(&self) -> usize { +// self.iter().map(|item| item.delta_dim()).sum() +// } +// fn mod_in(&self) -> usize { +// self.deref() +// .iter() +// .rev() +// .fold((1, 1), |(mod_out, mod_in), item| { +// update_mod_out_in(item, mod_out, mod_in) +// }) +// .1 +// } +// fn mod_out(&self) -> usize { +// self.deref() +// .iter() +// .rev() +// .fold((1, 1), |(mod_out, mod_in), item| { +// update_mod_out_in(item, mod_out, mod_in) +// }) +// .0 +// } +// fn apply_inplace( +// &self, +// index: usize, +// coordinates: &mut [f64], +// stride: usize, +// offset: usize, +// ) -> usize { +// self.iter().rev().fold(index, |index, item| { +// item.apply_inplace(index, coordinates, stride, offset) +// }) +// } +// fn apply_index(&self, index: usize) -> usize { +// self.iter() +// .rev() +// .fold(index, |index, item| item.apply_index(index)) +// } +// fn apply_indices_inplace(&self, indices: &mut [usize]) { +// for item in self.iter().rev() { +// item.apply_indices_inplace(indices); +// } +// } +// fn unapply_indices(&self, indices: &[T]) -> Vec { +// self.iter().fold(indices.to_vec(), |indices, item| { +// item.unapply_indices(&indices) +// }) +// } +//} +// +//impl AddOffset for Array +//where +// Item: AddOffset, +// Array: Deref + DerefMut + std::fmt::Debug, +//{ +// fn add_offset(&mut self, offset: usize) { +// for item in self.iter_mut() { +// item.add_offset(offset); +// } +// } +//} diff --git a/src/simplex.rs b/src/simplex.rs index cb9e2d345..3a693b971 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -154,7 +154,12 @@ impl Simplex { Self::Triangle => index / 4, } } - + pub fn apply_child_indices_inplace(&self, indices: &mut [usize]) { + match self { + Self::Line => indices.iter_mut().for_each(|i| *i /= 2), + Self::Triangle => indices.iter_mut().for_each(|i| *i /= 4), + } + } pub fn unapply_child_indices( &self, indices: impl Iterator, @@ -216,7 +221,12 @@ impl Simplex { Self::Triangle => index / 3, } } - + pub fn apply_edge_indices_inplace(&self, indices: &mut [usize]) { + match self { + Self::Line => indices.iter_mut().for_each(|i| *i /= 2), + Self::Triangle => indices.iter_mut().for_each(|i| *i /= 3), + } + } pub fn unapply_edge_indices( &self, indices: impl Iterator, diff --git a/src/tesselation.rs b/src/tesselation.rs index 9a1c35dd2..d7dc1a199 100644 --- a/src/tesselation.rs +++ b/src/tesselation.rs @@ -3,179 +3,245 @@ use crate::ops::{Concatenation, WithBoundsError}; use crate::simplex::Simplex; use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; -#[derive(Debug, Clone, PartialEq)] -pub struct WithShape { - map: M, - shapes: Vec, - dim_in: usize, +struct Tesselation { + shapes: Vec delta_dim: usize, len_out: usize, - len_in: usize, + maps: Vec<(usize, Vec)>, + reorder: Option>, } -impl WithShape { - pub fn new(map: M, shapes: Vec, len_in: usize) -> Result { - let dim_in: usize = shapes.iter().map(|simplex| simplex.dim()).sum(); - if dim_in < map.dim_in() { - Err(WithBoundsError::DimensionTooSmall) - } else if len_in != 0 && len_in.checked_rem(map.mod_in()) != Some(0) { - Err(WithBoundsError::LengthNotAMultipleOfRepetition) - } else { - Ok(Self::new_unchecked(map, shapes, len_in)) - } - } - pub(crate) fn new_unchecked(map: M, shapes: Vec, len_in: usize) -> Self { - let dim_in: usize = shapes.iter().map(|simplex| simplex.dim()).sum(); - let delta_dim = map.delta_dim(); - let len_out = if len_in == 0 { - 0 - } else { - len_in / map.mod_in() * map.mod_out() - }; +impl Tesselation { + pub fn new_identity(shapes: Vec, len: usize) -> Self { Self { - map, shapes, - dim_in, - delta_dim, - len_out, - len_in, + delta_dim: 0, + len_out: len, + maps: vec![(len, Vec::new()], + reorder: None, } } - pub fn get_unbounded(&self) -> &M { - &self.map + pub fn product(&self, other: &Self) -> Self { + unimplemented!{} } - pub fn into_unbounded(self) -> M { - self.map + pub fn concatenate(&self, other: &Self) -> Self { + unimplemented!{} + } + pub fn take(&self, indices: &[usize]) -> Self { + unimplemented!{} } pub fn children(&self) -> Self { - let mut map = self.map.clone(); - let mut offset = 0; - let mut len_in = self.len_in; - for simplex in &self.shapes { - let mut children = Elementary::new_children(*simplex); - children.add_offset(offset); - map.push_elementary(&children); - len_in *= simplex.nchildren(); - } - Self::new_unchecked(map, self.shapes.clone(), len_in) + unimplemented!{} } pub fn edges(&self) -> Self { - let mut map = self.map.clone(); - let mut offset = 0; - let mut len_in = self.len_in; - let mut shapes = Vec::new(); - for simplex in &self.shapes { - let mut edges = Elementary::new_edges(*simplex); - edges.add_offset(offset); - map.push_elementary(&edges); - len_in *= simplex.nedges(); - if let Some(edge_simplex) = simplex.edge_simplex() { - shapes.push(edge_simplex); - } - } - Self::new_unchecked(map, shapes, len_in) + unimplemented!{} } -} - -impl BoundedMap for WithShape { - fn len_in(&self) -> usize { - self.len_in - } - fn len_out(&self) -> usize { - self.len_out - } - fn dim_in(&self) -> usize { - self.dim_in + pub fn centroids(&self) -> Self { + unimplemented!{} } - fn delta_dim(&self) -> usize { - self.delta_dim + pub fn vertices(&self) -> Self { + unimplemented!{} } - fn apply_inplace_unchecked( - &self, - index: usize, - coordinates: &mut [f64], - stride: usize, - offset: usize, - ) -> usize { - self.map.apply_inplace(index, coordinates, stride, offset) + pub fn len(&self) -> usize { + self.maps.iter().map(|(n, _)| n).sum() } - fn apply_index_unchecked(&self, index: usize) -> usize { - self.map.apply_index(index) + pub fn dim(&self) -> usize { + self.shapes.iter().map(|shape| shape.dim()).sum() } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.map.apply_indices_inplace(indices) - } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - self.map.unapply_indices(indices) + pub fn apply_inplace(&self, mut index: usize, coords: &mut [f64], stride: usize) -> Option { + for (len, map) in &self.maps { + if index < len { + return map.apply_inplace(index, coords, stride, 0); + } + index -= len; + } + None } - fn is_identity(&self) -> bool { - self.map.is_identity() + pub fn unapply_indices(&self, indices: &[T]) -> Vec { + unimplemented!{} } } -#[derive(Debug, Clone, PartialEq)] -pub struct Tesselation(Concatenation>>); +// struct ProductTesselation(Vec); -impl Tesselation { - pub fn new_identity(shapes: Vec, len: usize) -> Self { - Self(Concatenation::new(vec![WithShape::new_unchecked( - vec![], - shapes, - len, - )])) - } - pub fn iter(&self) -> impl Iterator>> { - self.0.iter() - } - pub fn into_vec(self) -> Vec>> { - self.0.into_vec() - } - pub fn take(&self, indices: &[usize]) -> Self { - unimplemented! {} - } - pub fn children(&self) -> Result { - unimplemented! {} - } - pub fn edges(&self) -> Result { - unimplemented! {} - } - pub fn internal_edges_of_children(&self) -> Result { - unimplemented! {} - } -} +// struct Relative -macro_rules! dispatch { - ( - $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( - &$self:ident $(, $arg:ident: $ty:ty)* - ) $(-> $ret:ty)? - ) => { - #[inline] - $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { - $self.0.$fn($($arg),*) - } - }; - ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { - #[inline] - $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { - $self.0.$fn($($arg),*) - } - }; -} -impl BoundedMap for Tesselation { - dispatch! {fn len_out(&self) -> usize} - dispatch! {fn len_in(&self) -> usize} - dispatch! {fn dim_out(&self) -> usize} - dispatch! {fn dim_in(&self) -> usize} - dispatch! {fn delta_dim(&self) -> usize} - dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} - dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} - dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} - dispatch! {fn apply_index(&self, index: usize) -> Option} - dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} - dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} - dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} - dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} - dispatch! {fn is_identity(&self) -> bool} -} + + +//#[derive(Debug, Clone, PartialEq)] +//pub struct WithShape { +// map: M, +// shapes: Vec, +// dim_in: usize, +// delta_dim: usize, +// len_out: usize, +// len_in: usize, +//} +// +//impl WithShape { +// pub fn new(map: M, shapes: Vec, len_in: usize) -> Result { +// let dim_in: usize = shapes.iter().map(|simplex| simplex.dim()).sum(); +// if dim_in < map.dim_in() { +// Err(WithBoundsError::DimensionTooSmall) +// } else if len_in != 0 && len_in.checked_rem(map.mod_in()) != Some(0) { +// Err(WithBoundsError::LengthNotAMultipleOfRepetition) +// } else { +// Ok(Self::new_unchecked(map, shapes, len_in)) +// } +// } +// pub(crate) fn new_unchecked(map: M, shapes: Vec, len_in: usize) -> Self { +// let dim_in: usize = shapes.iter().map(|simplex| simplex.dim()).sum(); +// let delta_dim = map.delta_dim(); +// let len_out = if len_in == 0 { +// 0 +// } else { +// len_in / map.mod_in() * map.mod_out() +// }; +// Self { +// map, +// shapes, +// dim_in, +// delta_dim, +// len_out, +// len_in, +// } +// } +// pub fn get_unbounded(&self) -> &M { +// &self.map +// } +// pub fn into_unbounded(self) -> M { +// self.map +// } +// pub fn children(&self) -> Self { +// let mut map = self.map.clone(); +// let mut offset = 0; +// let mut len_in = self.len_in; +// for simplex in &self.shapes { +// let mut children = Elementary::new_children(*simplex); +// children.add_offset(offset); +// map.push_elementary(&children); +// len_in *= simplex.nchildren(); +// } +// Self::new_unchecked(map, self.shapes.clone(), len_in) +// } +// pub fn edges(&self) -> Self { +// let mut map = self.map.clone(); +// let mut offset = 0; +// let mut len_in = self.len_in; +// let mut shapes = Vec::new(); +// for simplex in &self.shapes { +// let mut edges = Elementary::new_edges(*simplex); +// edges.add_offset(offset); +// map.push_elementary(&edges); +// len_in *= simplex.nedges(); +// if let Some(edge_simplex) = simplex.edge_simplex() { +// shapes.push(edge_simplex); +// } +// } +// Self::new_unchecked(map, shapes, len_in) +// } +//} +// +//impl BoundedMap for WithShape { +// fn len_in(&self) -> usize { +// self.len_in +// } +// fn len_out(&self) -> usize { +// self.len_out +// } +// fn dim_in(&self) -> usize { +// self.dim_in +// } +// fn delta_dim(&self) -> usize { +// self.delta_dim +// } +// fn apply_inplace_unchecked( +// &self, +// index: usize, +// coordinates: &mut [f64], +// stride: usize, +// offset: usize, +// ) -> usize { +// self.map.apply_inplace(index, coordinates, stride, offset) +// } +// fn apply_index_unchecked(&self, index: usize) -> usize { +// self.map.apply_index(index) +// } +// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { +// self.map.apply_indices_inplace(indices) +// } +// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { +// self.map.unapply_indices(indices) +// } +// fn is_identity(&self) -> bool { +// self.map.is_identity() +// } +//} +// +//#[derive(Debug, Clone, PartialEq)] +//pub struct Tesselation(Concatenation>>); +// +//impl Tesselation { +// pub fn new_identity(shapes: Vec, len: usize) -> Self { +// Self(Concatenation::new(vec![WithShape::new_unchecked( +// vec![], +// shapes, +// len, +// )])) +// } +// pub fn iter(&self) -> impl Iterator>> { +// self.0.iter() +// } +// pub fn into_vec(self) -> Vec>> { +// self.0.into_vec() +// } +// pub fn take(&self, indices: &[usize]) -> Self { +// unimplemented! {} +// } +// pub fn children(&self) -> Result { +// unimplemented! {} +// } +// pub fn edges(&self) -> Result { +// unimplemented! {} +// } +// pub fn internal_edges_of_children(&self) -> Result { +// unimplemented! {} +// } +//} +// +//macro_rules! dispatch { +// ( +// $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( +// &$self:ident $(, $arg:ident: $ty:ty)* +// ) $(-> $ret:ty)? +// ) => { +// #[inline] +// $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { +// $self.0.$fn($($arg),*) +// } +// }; +// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { +// #[inline] +// $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { +// $self.0.$fn($($arg),*) +// } +// }; +//} +// +//impl BoundedMap for Tesselation { +// dispatch! {fn len_out(&self) -> usize} +// dispatch! {fn len_in(&self) -> usize} +// dispatch! {fn dim_out(&self) -> usize} +// dispatch! {fn dim_in(&self) -> usize} +// dispatch! {fn delta_dim(&self) -> usize} +// dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} +// dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} +// dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} +// dispatch! {fn apply_index(&self, index: usize) -> Option} +// dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} +// dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} +// dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} +// dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} +// dispatch! {fn is_identity(&self) -> bool} +//} From 7e54ef867375db355e814ef1faae9570ec38e0c8 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Tue, 28 Jun 2022 15:15:07 +0200 Subject: [PATCH 23/45] WIP --- src/elementary.rs | 427 ++++++++++++++++++++++++++++++---------------- src/lib.rs | 47 +---- src/ops.rs | 68 -------- 3 files changed, 280 insertions(+), 262 deletions(-) diff --git a/src/elementary.rs b/src/elementary.rs index 7738145f7..194b7d886 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -1,12 +1,51 @@ use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; -use crate::{AddOffset, Map, UnapplyIndicesData}; +use crate::{AddOffset, UnapplyIndicesData}; use num::Integer as _; +use std::ops::{Deref, DerefMut}; use std::rc::Rc; -#[inline] -const fn divmod(x: usize, y: usize) -> (usize, usize) { - (x / y, x % y) +pub trait UnboundedMap { + // Minimum dimension of the input coordinate. If the dimension of the input + // coordinate of [UnboundedMap::apply_inplace()] is larger than the minimum, then + // the map of the surplus is the identity map. + fn dim_in(&self) -> usize; + // Minimum dimension of the output coordinate. + fn dim_out(&self) -> usize { + self.dim_in() + self.delta_dim() + } + // Difference in dimension of the output and input coordinate. + fn delta_dim(&self) -> usize; + // Modulus of the input index. The map repeats itself at index `mod_in` + // and the output index is incremented with `in_index / mod_in * mod_out`. + fn mod_in(&self) -> usize; + // Modulus if the output index. + fn mod_out(&self) -> usize; + fn apply_mod_out_to_in(&self, n: usize) -> Option { + let (i, rem) = n.div_rem(&self.mod_out()); + (rem == 0).then(|| i * self.mod_in()) + } + fn apply_mod_in_to_out(&self, n: usize) -> Option { + let (i, rem) = n.div_rem(&self.mod_in()); + (rem == 0).then(|| i * self.mod_out()) + } + fn apply_inplace( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize; + fn apply_index(&self, index: usize) -> usize; + fn apply_indices_inplace(&self, indices: &mut [usize]) { + for index in indices.iter_mut() { + *index = self.apply_index(*index); + } + } + fn unapply_indices(&self, indices: &[T]) -> Vec; + fn is_identity(&self) -> bool { + self.mod_in() == 1 && self.mod_out() == 1 && self.dim_out() == 0 + } } fn coordinates_iter_mut( @@ -26,10 +65,10 @@ fn coordinates_iter_mut( }) } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Identity; -impl Map for Identity { +impl UnboundedMap for Identity { fn dim_in(&self) -> usize { 0 } @@ -67,6 +106,65 @@ impl AddOffset for Identity { fn add_offset(&mut self, _offset: usize) {} } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Offset(M, usize); + +impl UnboundedMap for Offset { + #[inline] + fn dim_in(&self) -> usize { + if self.0.dim_out() > 0 { + self.0.dim_in() + self.1 + } else { + 0 + } + } + #[inline] + fn delta_dim(&self) -> usize { + self.0.delta_dim() + } + #[inline] + fn mod_in(&self) -> usize { + self.0.mod_in() + } + #[inline] + fn mod_out(&self) -> usize { + self.0.mod_out() + } + #[inline] + fn apply_inplace( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { + self.0 + .apply_inplace(index, coordinates, stride, offset + self.1) + } + #[inline] + fn apply_index(&self, index: usize) -> usize { + self.0.apply_index(index) + } + #[inline] + fn apply_indices_inplace(&self, indices: &mut [usize]) { + self.0.apply_indices_inplace(indices); + } + #[inline] + fn unapply_indices(&self, indices: &[T]) -> Vec { + self.0.unapply_indices(indices) + } + #[inline] + fn is_identity(&self) -> bool { + self.0.is_identity() + } +} + +impl AddOffset for Offset { + fn add_offset(&mut self, offset: usize) { + self.1 += offset; + } +} + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Transpose(usize, usize); @@ -81,7 +179,7 @@ impl Transpose { } } -impl Map for Transpose { +impl UnboundedMap for Transpose { fn dim_in(&self) -> usize { 0 } @@ -108,16 +206,16 @@ impl Map for Transpose { self.apply_index(index) } fn apply_index(&self, index: usize) -> usize { - let (j, k) = divmod(index, self.1); - let (i, j) = divmod(j, self.0); + let (j, k) = index.div_rem(&self.1); + let (i, j) = j.div_rem(&self.0); (i * self.1 + k) * self.0 + j } fn unapply_indices(&self, indices: &[T]) -> Vec { indices .iter() .map(|index| { - let (j, k) = divmod(index.get(), self.0); - let (i, j) = divmod(j, self.1); + let (j, k) = index.get().div_rem(&self.0); + let (i, j) = j.div_rem(&self.1); index.set((i * self.0 + k) * self.1 + j) }) .collect() @@ -150,7 +248,7 @@ impl Take { } } -impl Map for Take { +impl UnboundedMap for Take { fn dim_in(&self) -> usize { 0 } @@ -179,7 +277,7 @@ impl Map for Take { indices .iter() .filter_map(|index| { - let (j, iout) = divmod(index.get(), self.len); + let (j, iout) = index.get().div_rem(&self.len); let offset = j * self.nindices; self.indices .iter() @@ -212,7 +310,7 @@ impl Slice { } } -impl Map for Slice { +impl UnboundedMap for Slice { fn dim_in(&self) -> usize { 0 } @@ -241,7 +339,7 @@ impl Map for Slice { indices .iter() .filter_map(|index| { - let (j, i) = divmod(index.get(), self.len_out); + let (j, i) = index.get().div_rem(&self.len_out); (self.start..self.start + self.len_in) .contains(&i) .then(|| index.set(i - self.start + j * self.len_in)) @@ -255,17 +353,17 @@ impl AddOffset for Slice { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Children(Simplex, usize); +pub struct Children(Simplex); impl Children { pub fn new(simplex: Simplex) -> Self { - Self(simplex, 0) + Self(simplex) } } -impl Map for Children { +impl UnboundedMap for Children { fn dim_in(&self) -> usize { - self.0.dim() + self.1 + self.0.dim() } fn delta_dim(&self) -> usize { 0 @@ -283,8 +381,7 @@ impl Map for Children { stride: usize, offset: usize, ) -> usize { - self.0 - .apply_child(index, coordinates, stride, offset + self.1) + self.0.apply_child(index, coordinates, stride, offset) } fn apply_index(&self, index: usize) -> usize { self.0.apply_child_index(index) @@ -300,12 +397,6 @@ impl Map for Children { } } -impl AddOffset for Children { - fn add_offset(&mut self, offset: usize) { - self.1 += offset; - } -} - impl From for Children { fn from(simplex: Simplex) -> Children { Children::new(simplex) @@ -313,17 +404,17 @@ impl From for Children { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Edges(pub Simplex, pub usize); +pub struct Edges(pub Simplex); impl Edges { pub fn new(simplex: Simplex) -> Self { - Self(simplex, 0) + Self(simplex) } } -impl Map for Edges { +impl UnboundedMap for Edges { fn dim_in(&self) -> usize { - self.0.edge_dim() + self.1 + self.0.edge_dim() } fn delta_dim(&self) -> usize { 1 @@ -341,8 +432,7 @@ impl Map for Edges { stride: usize, offset: usize, ) -> usize { - self.0 - .apply_edge(index, coordinates, stride, offset + self.1) + self.0.apply_edge(index, coordinates, stride, offset) } fn apply_index(&self, index: usize) -> usize { self.0.apply_edge_index(index) @@ -358,12 +448,6 @@ impl Map for Edges { } } -impl AddOffset for Edges { - fn add_offset(&mut self, offset: usize) { - self.1 += offset; - } -} - impl From for Edges { fn from(simplex: Simplex) -> Edges { Edges::new(simplex) @@ -375,7 +459,6 @@ pub struct UniformPoints { points: Rc<[FiniteF64]>, npoints: usize, point_dim: usize, - offset: usize, } impl UniformPoints { @@ -388,14 +471,13 @@ impl UniformPoints { points, npoints, point_dim, - offset: 0, } } } -impl Map for UniformPoints { +impl UnboundedMap for UniformPoints { fn dim_in(&self) -> usize { - self.offset + 0 } fn delta_dim(&self) -> usize { self.point_dim @@ -415,7 +497,7 @@ impl Map for UniformPoints { ) -> usize { let points: &[f64] = unsafe { std::mem::transmute(&self.points[..]) }; let point = &points[(index % self.npoints) * self.point_dim..][..self.point_dim]; - for coord in coordinates_iter_mut(coords, stride, offset + self.offset, self.point_dim, 0) { + for coord in coordinates_iter_mut(coords, stride, offset, self.point_dim, 0) { coord.copy_from_slice(point); } index / self.npoints @@ -434,20 +516,14 @@ impl Map for UniformPoints { } } -impl AddOffset for UniformPoints { - fn add_offset(&mut self, offset: usize) { - self.offset += offset; - } -} - #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum Elementary { Transpose(Transpose), Take(Take), Slice(Slice), - Children(Children), - Edges(Edges), - UniformPoints(UniformPoints), + Children(Offset), + Edges(Offset), + UniformPoints(Offset), } impl Elementary { @@ -465,42 +541,29 @@ impl Elementary { } #[inline] pub fn new_children(simplex: Simplex) -> Self { - Self::Children(Children::new(simplex)) + Self::Children(Offset(Children::new(simplex), 0)) } #[inline] pub fn new_edges(simplex: Simplex) -> Self { - Self::Edges(Edges::new(simplex)) + Self::Edges(Offset(Edges::new(simplex), 0)) } #[inline] pub fn new_uniform_points(points: impl Into>, point_dim: usize) -> Self { - Self::UniformPoints(UniformPoints::new(points, point_dim)) + Self::UniformPoints(Offset(UniformPoints::new(points, point_dim), 0)) } #[inline] fn offset_mut(&mut self) -> Option<&mut usize> { match self { - Self::Children(Children(_, ref mut offset)) => Some(offset), - Self::Edges(Edges(_, ref mut offset)) => Some(offset), - Self::UniformPoints(UniformPoints { ref mut offset, .. }) => Some(offset), + Self::Children(Offset(_, ref mut offset)) => Some(offset), + Self::Edges(Offset(_, ref mut offset)) => Some(offset), + Self::UniformPoints(Offset(_, ref mut offset)) => Some(offset), _ => None, } } - #[inline] - fn set_offset(&mut self, new_offset: usize) { - self.add_offset(new_offset); - } - #[inline] - pub fn with_offset(mut self, new_offset: usize) -> Self { - self.set_offset(new_offset); - self - } - //pub fn swap(&mut self, other: &Self) -> Option> { - // if self.mod_out() == 1 { - // - // } else { - // None - // } - //} - pub fn shift_left(&self, items: &[Self]) -> Option<(Option, Self, Vec)> { + pub fn shift_inner_to_outer( + &self, + items: &[Self], + ) -> Option<(Option, Self, Vec)> { if self.is_transpose() { return None; } @@ -509,10 +572,10 @@ impl Elementary { let mut queue: Vec = Vec::new(); let mut stride_out = 1; let mut stride_in = 1; - for mut item in items.iter().rev().cloned() { + for mut item in items.iter().cloned() { // Swap matching edges and children at the same offset. - if let Self::Edges(Edges(esimplex, eoffset)) = &item { - if let Self::Children(Children(ref mut csimplex, coffset)) = &mut target { + if let Self::Edges(Offset(Edges(esimplex), eoffset)) = &item { + if let Self::Children(Offset(Children(ref mut csimplex), coffset)) = &mut target { if eoffset == coffset && esimplex.edge_dim() == csimplex.dim() { if stride_in != 1 && self.mod_in() != 1 { shifted_items.push(Self::new_transpose(stride_in, self.mod_in())); @@ -525,7 +588,7 @@ impl Elementary { esimplex.swap_edges_children_map(), esimplex.nedges() * esimplex.nchildren(), )); - shifted_items.push(Self::Edges(Edges(*esimplex, *eoffset))); + shifted_items.push(Self::Edges(Offset(Edges(*esimplex), *eoffset))); *csimplex = *esimplex; stride_in = 1; stride_out = 1; @@ -582,13 +645,12 @@ impl Elementary { if stride_out != 1 && self.mod_in() != 1 { shifted_items.push(Self::new_transpose(self.mod_in(), stride_out)); } - let leading_transpose = if self.mod_out() == 1 || stride_out == 1 { + let outer_transpose = if self.mod_out() == 1 || stride_out == 1 { None } else { Some(Transpose::new(stride_out, self.mod_out())) }; - shifted_items.reverse(); - Some((leading_transpose, target, shifted_items)) + Some((outer_transpose, target, shifted_items)) } #[inline] const fn is_transpose(&self) -> bool { @@ -637,7 +699,7 @@ macro_rules! dispatch { }; } -impl Map for Elementary { +impl UnboundedMap for Elementary { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn mod_in(&self) -> usize} @@ -677,28 +739,116 @@ impl From for Elementary { impl From for Elementary { fn from(children: Children) -> Self { - Self::Children(children) + Self::Children(Offset(children, 0)) } } impl From for Elementary { fn from(edges: Edges) -> Self { - Self::Edges(edges) + Self::Edges(Offset(edges, 0)) } } impl From for Elementary { fn from(uniform_points: UniformPoints) -> Self { - Self::UniformPoints(uniform_points) + Self::UniformPoints(Offset(uniform_points, 0)) } } + +#[inline] +fn comp_dim_out_in(map: &M, dim_out: usize, dim_in: usize) -> (usize, usize) { + let n = map.dim_in().checked_sub(dim_out).unwrap_or(0); + (dim_out + map.delta_dim() + n, dim_in + n) +} + +#[inline] +fn comp_mod_out_in(map: &M, mod_out: usize, mod_in: usize) -> (usize, usize) { + let n = mod_out.lcm(&map.mod_in()); + (n / map.mod_in() * map.mod_out(), mod_in * n / mod_out) +} + +impl UnboundedMap for Array +where + M: UnboundedMap, + Array: Deref, +{ + #[inline] + fn dim_in(&self) -> usize { + self.iter() + .fold((0, 0), |(o, i), map| comp_dim_out_in(map, o, i)) + .1 + } + #[inline] + fn delta_dim(&self) -> usize { + self.iter().map(|map| map.delta_dim()).sum() + } + #[inline] + fn mod_in(&self) -> usize { + self.iter() + .fold((1, 1), |(o, i), map| comp_mod_out_in(map, o, i)) + .1 + } + #[inline] + fn mod_out(&self) -> usize { + self.iter() + .fold((1, 1), |(o, i), map| comp_mod_out_in(map, o, i)) + .0 + } + #[inline] + fn apply_inplace( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { + self.iter().fold(index, |index, map| { + map.apply_inplace(index, coords, stride, offset) + }) + } + #[inline] + fn apply_index(&self, index: usize) -> usize { + self.iter().fold(index, |index, map| map.apply_index(index)) + } + #[inline] + fn apply_indices_inplace(&self, indices: &mut [usize]) { + self.iter() + .for_each(|map| map.apply_indices_inplace(indices)); + } + #[inline] + fn unapply_indices(&self, indices: &[T]) -> Vec { + let mut iter = self.iter().rev(); + if let Some(map) = iter.next() { + iter.fold(map.unapply_indices(indices), |indices, map| { + map.unapply_indices(&indices) + }) + } else { + Vec::new() + } + } + #[inline] + fn is_identity(&self) -> bool { + self.iter().all(|map| map.is_identity()) + } +} + +impl AddOffset for Array +where + M: UnboundedMap + AddOffset, + Array: Deref + DerefMut, +{ + fn add_offset(&mut self, offset: usize) { + self.iter_mut().for_each(|map| map.add_offset(offset)); + } +} + #[cfg(test)] mod tests { use super::*; + use crate::assert_map_apply; use approx::assert_abs_diff_eq; use std::iter; use Simplex::*; - use crate::assert_map_apply; #[test] fn apply_transpose() { @@ -726,12 +876,12 @@ mod tests { #[test] fn apply_children_line() { - let item = Elementary::new_children(Line); + let mut item = Elementary::new_children(Line); assert_map_apply!(item, 0, [[0.0], [1.0]], 0, [[0.0], [0.5]]); assert_map_apply!(item, 1, [[0.0], [1.0]], 0, [[0.5], [1.0]]); assert_map_apply!(item, 2, [[0.0], [1.0]], 1, [[0.0], [0.5]]); - let item = item.with_offset(1); + item.add_offset(1); assert_map_apply!( item, 3, @@ -743,24 +893,24 @@ mod tests { #[test] fn apply_edges_line() { - let item = Elementary::new_edges(Line); + let mut item = Elementary::new_edges(Line); assert_map_apply!(item, 0, [[]], 0, [[1.0]]); assert_map_apply!(item, 1, [[]], 0, [[0.0]]); assert_map_apply!(item, 2, [[]], 1, [[1.0]]); - let item = item.with_offset(1); + item.add_offset(1); assert_map_apply!(item, 0, [[0.2]], 0, [[0.2, 1.0]]); } #[test] fn apply_uniform_points() { - let item = Elementary::new_uniform_points(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); + let mut item = Elementary::new_uniform_points(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); assert_map_apply!(item, 0, [[]], 0, [[1.0, 2.0]]); assert_map_apply!(item, 1, [[]], 0, [[3.0, 4.0]]); assert_map_apply!(item, 2, [[]], 0, [[5.0, 6.0]]); assert_map_apply!(item, 3, [[]], 1, [[1.0, 2.0]]); - let item = item.with_offset(1); + item.add_offset(1); assert_map_apply!(item, 0, [[7.0]], 0, [[7.0, 1.0, 2.0]]); } @@ -815,38 +965,17 @@ mod tests { ($a:expr, $b:expr $(, $simplex:ident)*) => {{ let a: &[Elementary] = &$a; let b: &[Elementary] = &$b; + let dim_in = 0 $(+ $simplex.dim())*; + let dim_out = a.dim_out(); println!("a: {a:?}"); println!("b: {b:?}"); - let tip_dim = 0 $(+ $simplex.dim())*; - // Determine the length of the sequence and the root dimension for sequence `a`. - let mut tip_len = 1; - let mut root_len = 1; - let mut root_dim = tip_dim; - for item in a.iter().rev() { - let i = (1..) - .into_iter() - .find(|i| (root_len * i) % item.mod_in() == 0) - .unwrap(); - tip_len *= i; - root_len *= i; - root_len = root_len / item.mod_in() * item.mod_out(); - assert!(item.dim_in() <= root_dim); - root_dim += item.delta_dim(); - } - assert!(tip_len > 0); - // Verify the length and the root dimension for sequence `b`. - let mut root_len_b = tip_len; - let mut root_dim_b = tip_dim; - for item in b.iter().rev() { - assert_eq!(root_len_b % item.mod_in(), 0); - root_len_b = root_len_b / item.mod_in() * item.mod_out(); - assert!(item.dim_in() <= root_dim_b); - root_dim_b += item.delta_dim(); - } - assert_eq!(root_len_b, root_len); - assert_eq!(root_dim_b, root_dim); + assert_eq!(a.mod_in(), b.mod_in()); + assert_eq!(a.mod_out(), b.mod_out()); + assert_eq!(a.dim_in(), dim_in); + assert_eq!(b.dim_in(), dim_in); + assert_eq!(a.delta_dim(), b.delta_dim()); // Build coords: the outer product of the vertices of the given simplices, zero-padded - // to the dimension of the root. + // to the out dimension. let coords = iter::once([]); $( let coords = coords.flat_map(|coord| { @@ -856,59 +985,61 @@ mod tests { .map(move |vert| [&coord, vert].concat()) }); )* - let pad: Vec = iter::repeat(0.0).take(root_dim - tip_dim).collect(); + let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); - // Test if every tip index and coordinate maps to the same root index and coordinate. - for itip in 0..2 * tip_len { + // Test if every tip index and coordinate maps to the same out index and coordinate. + for iin in 0..2 * a.mod_in() { let mut crds_a = coords.clone(); let mut crds_b = coords.clone(); - let iroot_a = a.iter().rev().fold(itip, |i, item| item.apply_inplace(i, &mut crds_a, root_dim, 0)); - let iroot_b = b.iter().rev().fold(itip, |i, item| item.apply_inplace(i, &mut crds_b, root_dim, 0)); - assert_eq!(iroot_a, iroot_b, "itip={itip}"); + let iout_a = a.apply_inplace(iin, &mut crds_a, dim_out, 0); + let iout_b = b.apply_inplace(iin, &mut crds_b, dim_out, 0); + assert_eq!(iout_a, iout_b, "iin={iin}"); assert_abs_diff_eq!(crds_a[..], crds_b[..]); } }}; } - macro_rules! assert_shift_left { + macro_rules! assert_shift_inner_to_outer { ($($item:expr),*; $($simplex:ident),*) => {{ let unshifted = [$(Elementary::from($item),)*]; - let (ltrans, litem, lchain) = unshifted.last().unwrap().shift_left(&unshifted[..unshifted.len()-1]).unwrap(); + let (ltrans, litem, lchain) = unshifted.first().unwrap().shift_inner_to_outer(&unshifted[1..]).unwrap(); let mut shifted: Vec = Vec::new(); + shifted.extend(lchain.into_iter()); + shifted.push(litem); if let Some(ltrans) = ltrans { shifted.push(ltrans.into()); } - shifted.push(litem); - shifted.extend(lchain.into_iter()); assert_equiv_maps!(&shifted[..], &unshifted[..] $(, $simplex)*); }}; } #[test] - fn shift_left() { - assert_shift_left!( - Transpose::new(4, 3), Elementary::new_take(vec![0, 1], 3); + fn shift_inner_to_outer() { + assert_shift_inner_to_outer!( + Elementary::new_take(vec![0, 1], 3), Transpose::new(4, 3); ); - assert_shift_left!( - Transpose::new(3, 5), Transpose::new(5, 4*3), Elementary::new_take(vec![0, 1], 3); + assert_shift_inner_to_outer!( + Elementary::new_take(vec![0, 1], 3), Transpose::new(5, 4*3), Transpose::new(3, 5); ); - assert_shift_left!( - Transpose::new(5, 4), Transpose::new(5*4, 3), Elementary::new_take(vec![0, 1], 3); + assert_shift_inner_to_outer!( + Elementary::new_take(vec![0, 1], 3), Transpose::new(5*4, 3), Transpose::new(5, 4); ); - assert_shift_left!( - Elementary::new_children(Line).with_offset(1), Elementary::new_children(Line); + assert_shift_inner_to_outer!( + Elementary::new_children(Line), + {let mut elem = Elementary::new_children(Line); elem.add_offset(1); elem}; Line, Line ); - assert_shift_left!( - Elementary::new_edges(Line), Elementary::new_children(Line); + assert_shift_inner_to_outer!( + Elementary::new_children(Line), Elementary::new_edges(Line); Line ); - assert_shift_left!( - Elementary::new_edges(Line).with_offset(1), Elementary::new_children(Line); + assert_shift_inner_to_outer!( + Elementary::new_children(Line), + {let mut elem = Elementary::new_edges(Line); elem.add_offset(1); elem}; Line ); - assert_shift_left!( - Elementary::new_edges(Triangle), Elementary::new_children(Line); + assert_shift_inner_to_outer!( + Elementary::new_children(Line), Elementary::new_edges(Triangle); Line ); } diff --git a/src/lib.rs b/src/lib.rs index ad82ccdc2..1241086bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,56 +1,11 @@ pub mod elementary; pub mod finite_f64; -pub mod ops; +//pub mod ops; //pub mod relative; pub mod simplex; //pub mod tesselation; //pub mod topology; -use num::Integer as _; - -pub trait Map { - // Minimum dimension of the input coordinate. If the dimension of the input - // coordinate of [Map::apply_inplace()] is larger than the minimum, then - // the map of the surplus is the identity map. - fn dim_in(&self) -> usize; - // Minimum dimension of the output coordinate. - fn dim_out(&self) -> usize { - self.dim_in() + self.delta_dim() - } - // Difference in dimension of the output and input coordinate. - fn delta_dim(&self) -> usize; - // Modulus of the input index. The map repeats itself at index `mod_in` - // and the output index is incremented with `in_index / mod_in * mod_out`. - fn mod_in(&self) -> usize; - // Modulus if the output index. - fn mod_out(&self) -> usize; - fn apply_mod_out_to_in(&self, n: usize) -> Option { - let (i, rem) = n.div_rem(&self.mod_out()); - (rem == 0).then(|| i * self.mod_in()) - } - fn apply_mod_in_to_out(&self, n: usize) -> Option { - let (i, rem) = n.div_rem(&self.mod_in()); - (rem == 0).then(|| i * self.mod_out()) - } - fn apply_inplace( - &self, - index: usize, - coordinates: &mut [f64], - stride: usize, - offset: usize, - ) -> usize; - fn apply_index(&self, index: usize) -> usize; - fn apply_indices_inplace(&self, indices: &mut [usize]) { - for index in indices.iter_mut() { - *index = self.apply_index(*index); - } - } - fn unapply_indices(&self, indices: &[T]) -> Vec; - fn is_identity(&self) -> bool { - self.mod_in() == 1 && self.mod_out() == 1 && self.dim_out() == 0 - } -} - pub trait AddOffset { fn add_offset(&mut self, offset: usize); } diff --git a/src/ops.rs b/src/ops.rs index 2b90b24f8..dc3e7e6bd 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -2,74 +2,6 @@ use crate::{Map, UnapplyIndicesData}; use num::Integer as _; use std::ops::Deref; -#[derive(Debug, Clone, PartialEq)] -pub struct Offset { - map: M, - offset: usize, -} - -impl Map for Offset { - #[inline] - fn dim_in(&self) -> usize { - if self.map.dim_out() > 0 { - self.map.dim_in() + self.offset - } else { - 0 - } - } - #[inline] - fn delta_dim(&self) -> usize { - self.map.delta_dim() - } - #[inline] - fn mod_in(&self) -> usize { - self.map.mod_in() - } - #[inline] - fn mod_out(&self) -> usize { - self.map.mod_out() - } - #[inline] - fn apply_inplace( - &self, - index: usize, - coordinates: &mut [f64], - stride: usize, - offset: usize, - ) -> usize { - self.map - .apply_inplace(index, coordinates, stride, offset + self.offset) - } - #[inline] - fn apply_index(&self, index: usize) -> usize { - self.map.apply_index(index) - } - #[inline] - fn apply_indices_inplace(&self, indices: &mut [usize]) { - self.map.apply_indices_inplace(indices); - } - #[inline] - fn unapply_indices(&self, indices: &[T]) -> Vec { - self.map.unapply_indices(indices) - } - #[inline] - fn is_identity(&self) -> bool { - self.map.is_identity() - } -} - -#[inline] -fn comp_dim_out_in(map: &M, dim_out: usize, dim_in: usize) -> (usize, usize) { - let n = map.dim_in().checked_sub(dim_out).unwrap_or(0); - (dim_out + map.delta_dim() + n, dim_in + n) -} - -#[inline] -fn comp_mod_out_in(map: &M, mod_out: usize, mod_in: usize) -> (usize, usize) { - let n = mod_out.lcm(&map.mod_in()); - (n / map.mod_in() * map.mod_out(), mod_in * n / mod_out) -} - #[derive(Debug, Clone, PartialEq)] pub struct BinaryComposition { inner: Inner, From 47080700f8cdd24bcec764d98a64c92d534dbc88 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Tue, 28 Jun 2022 23:56:36 +0200 Subject: [PATCH 24/45] WIP --- src/elementary.rs | 152 ++++++-- src/lib.rs | 87 ++++- src/ops.rs | 913 +++++++++++---------------------------------- src/tesselation.rs | 36 ++ 4 files changed, 471 insertions(+), 717 deletions(-) diff --git a/src/elementary.rs b/src/elementary.rs index 194b7d886..f558b4df5 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -1,6 +1,6 @@ use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; -use crate::{AddOffset, UnapplyIndicesData}; +use crate::{Map, Error, AddOffset, UnapplyIndicesData}; use num::Integer as _; use std::ops::{Deref, DerefMut}; use std::rc::Rc; @@ -32,7 +32,7 @@ pub trait UnboundedMap { fn apply_inplace( &self, index: usize, - coordinates: &mut [f64], + coords: &mut [f64], stride: usize, offset: usize, ) -> usize; @@ -48,7 +48,7 @@ pub trait UnboundedMap { } } -fn coordinates_iter_mut( +fn coords_iter_mut( flat: &mut [f64], stride: usize, offset: usize, @@ -84,7 +84,7 @@ impl UnboundedMap for Identity { fn apply_inplace( &self, index: usize, - _coordinates: &mut [f64], + _coords: &mut [f64], _stride: usize, _offset: usize, ) -> usize { @@ -134,12 +134,12 @@ impl UnboundedMap for Offset { fn apply_inplace( &self, index: usize, - coordinates: &mut [f64], + coords: &mut [f64], stride: usize, offset: usize, ) -> usize { self.0 - .apply_inplace(index, coordinates, stride, offset + self.1) + .apply_inplace(index, coords, stride, offset + self.1) } #[inline] fn apply_index(&self, index: usize) -> usize { @@ -199,7 +199,7 @@ impl UnboundedMap for Transpose { fn apply_inplace( &self, index: usize, - _coordinates: &mut [f64], + _coords: &mut [f64], _stride: usize, _offset: usize, ) -> usize { @@ -235,7 +235,9 @@ pub struct Take { impl Take { pub fn new(indices: impl Into>, len: usize) -> Self { + // TODO: return err if indices.is_empty() let indices = indices.into(); + assert!(!indices.is_empty()); let nindices = indices.len(); Take { indices, @@ -264,7 +266,7 @@ impl UnboundedMap for Take { fn apply_inplace( &self, index: usize, - _coordinates: &mut [f64], + _coords: &mut [f64], _stride: usize, _offset: usize, ) -> usize { @@ -301,6 +303,7 @@ pub struct Slice { impl Slice { pub fn new(start: usize, len_in: usize, len_out: usize) -> Self { + assert!(len_in > 0); assert!(len_out >= start + len_in); Slice { start, @@ -326,7 +329,7 @@ impl UnboundedMap for Slice { fn apply_inplace( &self, index: usize, - _coordinates: &mut [f64], + _coords: &mut [f64], _stride: usize, _offset: usize, ) -> usize { @@ -377,11 +380,11 @@ impl UnboundedMap for Children { fn apply_inplace( &self, index: usize, - coordinates: &mut [f64], + coords: &mut [f64], stride: usize, offset: usize, ) -> usize { - self.0.apply_child(index, coordinates, stride, offset) + self.0.apply_child(index, coords, stride, offset) } fn apply_index(&self, index: usize) -> usize { self.0.apply_child_index(index) @@ -428,11 +431,11 @@ impl UnboundedMap for Edges { fn apply_inplace( &self, index: usize, - coordinates: &mut [f64], + coords: &mut [f64], stride: usize, offset: usize, ) -> usize { - self.0.apply_edge(index, coordinates, stride, offset) + self.0.apply_edge(index, coords, stride, offset) } fn apply_index(&self, index: usize) -> usize { self.0.apply_edge_index(index) @@ -497,7 +500,7 @@ impl UnboundedMap for UniformPoints { ) -> usize { let points: &[f64] = unsafe { std::mem::transmute(&self.points[..]) }; let point = &points[(index % self.npoints) * self.point_dim..][..self.point_dim]; - for coord in coordinates_iter_mut(coords, stride, offset, self.point_dim, 0) { + for coord in coords_iter_mut(coords, stride, offset, self.point_dim, 0) { coord.copy_from_slice(point); } index / self.npoints @@ -529,27 +532,27 @@ pub enum Elementary { impl Elementary { #[inline] pub fn new_transpose(len1: usize, len2: usize) -> Self { - Self::Transpose(Transpose::new(len1, len2)) + Transpose::new(len1, len2).into() } #[inline] pub fn new_take(indices: impl Into>, len: usize) -> Self { - Self::Take(Take::new(indices, len)) + Take::new(indices, len).into() } #[inline] pub fn new_slice(start: usize, len_in: usize, len_out: usize) -> Self { - Self::Slice(Slice::new(start, len_in, len_out)) + Slice::new(start, len_in, len_out).into() } #[inline] pub fn new_children(simplex: Simplex) -> Self { - Self::Children(Offset(Children::new(simplex), 0)) + Children::new(simplex).into() } #[inline] pub fn new_edges(simplex: Simplex) -> Self { - Self::Edges(Offset(Edges::new(simplex), 0)) + Edges::new(simplex).into() } #[inline] pub fn new_uniform_points(points: impl Into>, point_dim: usize) -> Self { - Self::UniformPoints(Offset(UniformPoints::new(points, point_dim), 0)) + UniformPoints::new(points, point_dim).into() } #[inline] fn offset_mut(&mut self) -> Option<&mut usize> { @@ -704,7 +707,7 @@ impl UnboundedMap for Elementary { dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn mod_in(&self) -> usize} dispatch! {fn mod_out(&self) -> usize} - dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut[f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coords: &mut[f64], stride: usize, offset: usize) -> usize} dispatch! {fn apply_index(&self, index: usize) -> usize} dispatch! {fn apply_indices_inplace(&self, indices: &mut [usize])} dispatch! {fn unapply_indices(&self, indices: &[T]) -> Vec} @@ -842,14 +845,119 @@ where } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct WithBounds { + map: M, + dim_in: usize, + delta_dim: usize, + len_in: usize, + len_out: usize, +} + +impl WithBounds { + pub fn new(map: M, dim_in: usize, len_in: usize) -> Result { + if dim_in < map.dim_in() { + Err(Error::DimensionMismatch) + } else if len_in % map.mod_in() != 0 { + Err(Error::LengthMismatch) + } else { + Ok(Self::new_unchecked(map, dim_in, len_in)) + } + } + pub fn new_unchecked(map: M, dim_in: usize, len_in: usize) -> Self { + let delta_dim = map.delta_dim(); + let len_out = len_in / map.mod_in() * map.mod_out(); + Self { map, dim_in, delta_dim, len_in, len_out } + } +} + +impl Map for WithBounds { + #[inline] + fn dim_in(&self) -> usize { + self.dim_in + } + #[inline] + fn delta_dim(&self) -> usize { + self.delta_dim + } + #[inline] + fn len_in(&self) -> usize { + self.len_in + } + #[inline] + fn len_out(&self) -> usize { + self.len_out + } + #[inline] + fn apply_inplace_unchecked( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { + self.map.apply_inplace(index, coords, stride, offset) + } + #[inline] + fn apply_index_unchecked(&self, index: usize) -> usize { + self.map.apply_index(index) + } + #[inline] + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.map.apply_indices_inplace(indices) + } + #[inline] + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + self.map.unapply_indices(indices) + } + #[inline] + fn is_identity(&self) -> bool { + self.map.is_identity() + } +} + #[cfg(test)] mod tests { use super::*; - use crate::assert_map_apply; use approx::assert_abs_diff_eq; use std::iter; use Simplex::*; + macro_rules! assert_map_apply { + ($item:expr, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ + use std::borrow::Borrow; + let item = $item.borrow(); + let incoords = $incoords; + let outcoords = $outcoords; + assert_eq!(incoords.len(), outcoords.len()); + let stride; + let mut work: Vec<_>; + if incoords.len() == 0 { + stride = item.dim_out(); + work = Vec::with_capacity(0); + } else { + stride = outcoords[0].len(); + work = iter::repeat(-1.0).take(outcoords.len() * stride).collect(); + for (work, incoord) in iter::zip(work.chunks_mut(stride), incoords.iter()) { + work[..incoord.len()].copy_from_slice(incoord); + } + } + assert_eq!(item.apply_inplace($inidx, &mut work, stride, 0), $outidx); + for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { + assert_abs_diff_eq!(actual[..], desired[..]); + } + }}; + ($item:expr, $inidx:expr, $outidx:expr) => {{ + use std::borrow::Borrow; + let item = $item.borrow(); + let mut work = Vec::with_capacity(0); + assert_eq!( + item.apply_inplace($inidx, &mut work, item.dim_out(), 0), + $outidx + ); + }}; + } + #[test] fn apply_transpose() { let item = Elementary::new_transpose(3, 2); diff --git a/src/lib.rs b/src/lib.rs index 1241086bf..3684af22d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,92 @@ pub mod elementary; pub mod finite_f64; -//pub mod ops; +pub mod ops; //pub mod relative; pub mod simplex; //pub mod tesselation; //pub mod topology; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + Empty, + DimensionMismatch, + LengthMismatch, +} + +impl std::error::Error for Error {} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Empty => write!(f, "The input array is empty."), + Self::DimensionMismatch => write!(f, "The dimensions of the maps differ."), + Self::LengthMismatch => write!(f, "The lengths of the maps differ."), + } + } +} + + +pub trait Map { + fn len_out(&self) -> usize; + fn len_in(&self) -> usize; + fn dim_out(&self) -> usize { + self.dim_in() + self.delta_dim() + } + fn dim_in(&self) -> usize; + fn delta_dim(&self) -> usize; + fn apply_inplace_unchecked( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> usize; + fn apply_inplace( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> Option { + if index < self.len_in() && offset + self.dim_out() <= stride { + Some(self.apply_inplace_unchecked(index, coords, stride, offset)) + } else { + None + } + } + fn apply_index_unchecked(&self, index: usize) -> usize; + fn apply_index(&self, index: usize) -> Option { + if index < self.len_in() { + Some(self.apply_index_unchecked(index)) + } else { + None + } + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + for index in indices.iter_mut() { + *index = self.apply_index_unchecked(*index); + } + } + fn apply_indices(&self, indices: &[usize]) -> Option> { + if indices.iter().all(|index| *index < self.len_in()) { + let mut indices = indices.to_vec(); + self.apply_indices_inplace_unchecked(&mut indices); + Some(indices) + } else { + None + } + } + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec; + fn unapply_indices(&self, indices: &[T]) -> Option> { + if indices.iter().all(|index| index.get() < self.len_out()) { + Some(self.unapply_indices_unchecked(indices)) + } else { + None + } + } + fn is_identity(&self) -> bool; +} + pub trait AddOffset { fn add_offset(&mut self, offset: usize); } @@ -46,7 +127,7 @@ macro_rules! assert_map_apply { work[..incoord.len()].copy_from_slice(incoord); } } - assert_eq!(item.apply_inplace($inidx, &mut work, stride, 0), $outidx); + assert_eq!(item.apply_inplace($inidx, &mut work, stride, 0), $outidx).unwrap(); for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { assert_abs_diff_eq!(actual[..], desired[..]); } @@ -56,7 +137,7 @@ macro_rules! assert_map_apply { let item = $item.borrow(); let mut work = Vec::with_capacity(0); assert_eq!( - item.apply_inplace($inidx, &mut work, item.dim_out(), 0), + item.apply_inplace($inidx, &mut work, item.dim_out(), 0).unwrap(), $outidx ); }}; diff --git a/src/ops.rs b/src/ops.rs index dc3e7e6bd..dcb215b37 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,61 +1,69 @@ -use crate::{Map, UnapplyIndicesData}; -use num::Integer as _; +use crate::{Error, Map, UnapplyIndicesData}; use std::ops::Deref; #[derive(Debug, Clone, PartialEq)] -pub struct BinaryComposition { - inner: Inner, - outer: Outer, +pub struct BinaryComposition(Inner, Outer); + +impl BinaryComposition { + pub fn new(inner: Inner, outer: Outer) -> Result { + if inner.dim_out() != outer.dim_in() { + Err(Error::DimensionMismatch) + } else if inner.len_out() != outer.len_in() { + Err(Error::LengthMismatch) + } else { + Ok(Self(inner, outer)) + } + } } impl Map for BinaryComposition { #[inline] fn dim_in(&self) -> usize { - comp_dim_out_in(&self.outer, self.inner.dim_out(), self.inner.dim_in()).1 + self.0.dim_in() } #[inline] fn delta_dim(&self) -> usize { - self.inner.delta_dim() + self.outer.delta_dim() + self.0.delta_dim() + self.1.delta_dim() } #[inline] - fn mod_in(&self) -> usize { - comp_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).0 + fn len_in(&self) -> usize { + self.0.len_in() } #[inline] - fn mod_out(&self) -> usize { - comp_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).1 + fn len_out(&self) -> usize { + self.1.len_out() } #[inline] - fn apply_inplace( + fn apply_inplace_unchecked( &self, index: usize, - coordinates: &mut [f64], + coords: &mut [f64], stride: usize, offset: usize, ) -> usize { - let index = self.inner.apply_inplace(index, coordinates, stride, offset); - self.outer.apply_inplace(index, coordinates, stride, offset) + let index = self.0.apply_inplace_unchecked(index, coords, stride, offset); + self.1.apply_inplace_unchecked(index, coords, stride, offset) } #[inline] - fn apply_index(&self, index: usize) -> usize { - self.outer.apply_index(self.inner.apply_index(index)) + fn apply_index_unchecked(&self, index: usize) -> usize { + self.1.apply_index_unchecked(self.0.apply_index_unchecked(index)) } #[inline] - fn apply_indices_inplace(&self, indices: &mut [usize]) { - self.inner.apply_indices_inplace(indices); - self.outer.apply_indices_inplace(indices); + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.0.apply_indices_inplace_unchecked(indices); + self.1.apply_indices_inplace_unchecked(indices); } #[inline] - fn unapply_indices(&self, indices: &[T]) -> Vec { - self.inner - .unapply_indices(&self.outer.unapply_indices(indices)) + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + self.0.unapply_indices_unchecked(&self.1.unapply_indices_unchecked(indices)) } #[inline] fn is_identity(&self) -> bool { - self.inner.is_identity() && self.outer.is_identity() + self.0.is_identity() && self.1.is_identity() } } +#[derive(Debug, Clone, PartialEq)] pub struct UniformComposition(Array) where M: Map, @@ -66,6 +74,28 @@ where M: Map, Array: Deref, { + pub fn new(array: Array) -> Result { + let mut iter = array.iter(); + if let Some(map) = iter.next() { + let mut dim_out = map.dim_out(); + let mut len_out = map.len_out(); + for map in iter { + if map.dim_in() != dim_out { + return Err(Error::DimensionMismatch); + } else if map.len_in() != len_out { + return Err(Error::LengthMismatch); + } + dim_out = map.dim_out(); + len_out = map.len_out(); + } + Ok(Self(array)) + } else { + Err(Error::Empty) + } + } + pub fn new_unchecked(array: Array) -> Self { + Self(array) + } pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, M> { self.0.iter() } @@ -78,28 +108,26 @@ where { #[inline] fn dim_in(&self) -> usize { - self.iter() - .fold((0, 0), |(o, i), map| comp_dim_out_in(map, o, i)) - .1 + self.0.first().unwrap().dim_in() + } + #[inline] + fn dim_out(&self) -> usize { + self.0.last().unwrap().dim_out() } #[inline] fn delta_dim(&self) -> usize { - self.iter().map(|map| map.delta_dim()).sum() + self.dim_out() - self.dim_in() } #[inline] - fn mod_in(&self) -> usize { - self.iter() - .fold((1, 1), |(o, i), map| comp_mod_out_in(map, o, i)) - .1 + fn len_in(&self) -> usize { + self.0.first().unwrap().len_in() } #[inline] - fn mod_out(&self) -> usize { - self.iter() - .fold((1, 1), |(o, i), map| comp_mod_out_in(map, o, i)) - .0 + fn len_out(&self) -> usize { + self.0.last().unwrap().len_out() } #[inline] - fn apply_inplace( + fn apply_inplace_unchecked( &self, index: usize, coords: &mut [f64], @@ -107,28 +135,23 @@ where offset: usize, ) -> usize { self.iter().fold(index, |index, map| { - map.apply_inplace(index, coords, stride, offset) + map.apply_inplace_unchecked(index, coords, stride, offset) }) } #[inline] - fn apply_index(&self, index: usize) -> usize { - self.iter().fold(index, |index, map| map.apply_index(index)) + fn apply_index_unchecked(&self, index: usize) -> usize { + self.iter().fold(index, |index, map| map.apply_index_unchecked(index)) } #[inline] - fn apply_indices_inplace(&self, indices: &mut [usize]) { + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { self.iter() - .for_each(|map| map.apply_indices_inplace(indices)); + .for_each(|map| map.apply_indices_inplace_unchecked(indices)); } #[inline] - fn unapply_indices(&self, indices: &[T]) -> Vec { + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { let mut iter = self.iter().rev(); - if let Some(map) = iter.next() { - iter.fold(map.unapply_indices(indices), |indices, map| { - map.unapply_indices(&indices) - }) - } else { - Vec::new() - } + let indices = iter.next().unwrap().unapply_indices_unchecked(indices); + iter.fold(indices, |indices, map| map.unapply_indices_unchecked(&indices)) } #[inline] fn is_identity(&self) -> bool { @@ -136,268 +159,182 @@ where } } -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ConcatError { - ModulusNotAMultiple, - DeltaDimsDiffer, -} - -impl std::error::Error for ConcatError {} +#[derive(Debug, Clone, PartialEq)] +pub struct BinaryConcat(M0, M1); -impl std::fmt::Display for ConcatError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::ModulusNotAMultiple => write!( - f, - "The given output modulus is not a multiple of the output moduli of the given maps.", - ), - Self::DeltaDimsDiffer => write!( - f, "Cannot concatenate maps with different delta dimensions."), +impl BinaryConcat { + pub fn new(map0: M0, map1: M1) -> Result { + if map0.dim_in() != map1.dim_in() || map0.dim_out() != map1.dim_out() { + Err(Error::DimensionMismatch) + } else if map0.len_out() != map1.len_out() { + Err(Error::LengthMismatch) + } else { + Ok(Self(map0, map1)) } } -} - -//fold_map! {BinaryConcat, UniformConcat; -// type Item = (M, usize); -// dim_in -// |(map, _)| map.dim_in() -// |dim_in, (map, _)| std::cmp::max(dim_in, map.dim_in()) -// |dim_in| dim_in -// delta_dim -// |(map, _)| map.delta_dim() -// |delta_dim, (map, _)| delta_dim + map.delta_dim() -// |delta_dim| delta_dim -// mod_out_in -// |(map, len)| (map.apply_mod_in_to_out(len).unwrap(), len) -// |(mod_out, mod_in), (_, len)| (mod_out, mod_in + len) -// |state| state -// apply -// |apply, index, (map, len)| { -// let (i, j) = index.div_rem(&self.mod_in()); -// if j < len { -// (apply(map, j + i * len), i, j, len) -// } else { -// (index, i, j, len) -// } -// } -// |apply, (index, i, j, k), (map, len)| { -// if k <= j && j < k + len { -// (apply(map, j + i * len), i, j, k + len) -// } else { -// (index, i, j, k + len) -// } -// } -// |(index, i, j, k)| done -// unapply -// |indices, (map, len)| ( -// map.unapply_indices(indices) -// .into_iter() -// .map(|i| i.set(i.get() / len * self.mod_in() + i.get() % len)) -// .collect::>(), -// len -// ) -// |(mut indices, k), (map, len)| ( -// indices.extend( -// map.unapply_indices(indices) -// .into_iter() -// .map(|i| i.set(i.get() / len * self.mod_in() + i.get() % len + k))), -// k + len -// ) -// |(indices, k)| indices -//} - -pub struct BinaryConcat { - map1: M1, - map2: M2, - mod_out: usize, - mod_in1: usize, - mod_in2: usize, -} - -impl BinaryConcat { - pub fn new(map1: M1, map2: M2, mod_out: usize) -> Result { - let mod_in1 = map1 - .apply_mod_out_to_in(mod_out) - .ok_or(ConcatError::ModulusNotAMultiple)?; - let mod_in2 = map2 - .apply_mod_out_to_in(mod_out) - .ok_or(ConcatError::ModulusNotAMultiple)?; - if map1.delta_dim() != map2.delta_dim() { - return Err(ConcatError::DeltaDimsDiffer); - } - Ok(Self { - map1, - map2, - mod_out, - mod_in1, - mod_in2, - }) + pub fn new_unchecked(map0: M0, map1: M1) -> Self { + Self(map0, map1) } } -impl Map for BinaryConcat { +impl Map for BinaryConcat { #[inline] fn dim_in(&self) -> usize { - std::cmp::max(self.map1.dim_in(), self.map2.dim_in()) + self.0.dim_in() + } + #[inline] + fn dim_out(&self) -> usize { + self.0.dim_out() } #[inline] fn delta_dim(&self) -> usize { - self.map1.delta_dim() + self.0.delta_dim() } #[inline] - fn mod_in(&self) -> usize { - self.mod_in1 + self.mod_in2 + fn len_in(&self) -> usize { + self.0.len_in() + self.1.len_in() } #[inline] - fn mod_out(&self) -> usize { - self.mod_out + fn len_out(&self) -> usize { + self.0.len_out() } #[inline] - fn apply_inplace( + fn apply_inplace_unchecked( &self, index: usize, coords: &mut [f64], stride: usize, offset: usize, ) -> usize { - let (i, j) = index.div_rem(&self.mod_in()); - if j < self.mod_in1 { - self.map1 - .apply_inplace(j + i * self.mod_in1, coords, stride, offset) + if index < self.0.len_in() { + self.0.apply_inplace_unchecked(index, coords, stride, offset) } else { - self.map2 - .apply_inplace(j - self.mod_in1 + i * self.mod_in1, coords, stride, offset) + self.1.apply_inplace_unchecked(index - self.0.len_in(), coords, stride, offset) } } #[inline] - fn apply_index(&self, index: usize) -> usize { - let (i, j) = index.div_rem(&self.mod_in()); - if let Some(j) = j.checked_sub(self.mod_in1) { - self.map2.apply_index(j + i * self.mod_in2) + fn apply_index_unchecked(&self, index: usize) -> usize { + if index < self.0.len_in() { + self.0.apply_index_unchecked(index) } else { - self.map1.apply_index(j + i * self.mod_in1) + self.1.apply_index_unchecked(index - self.0.len_in()) } } #[inline] - fn unapply_indices(&self, indices: &[T]) -> Vec { - let mut result: Vec<_> = self - .map1 - .unapply_indices(indices) - .into_iter() - .map(|i| i.set(i.get() / self.mod_in1 * self.mod_in() + i.get() % self.mod_in1)) - .collect(); - result.extend(self.map2.unapply_indices(indices).into_iter().map(|i| { - i.set(i.get() / self.mod_in2 * self.mod_in() + self.mod_in1 + i.get() % self.mod_in2) - })); + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + let mut result = self.0.unapply_indices_unchecked(indices); + result.extend(self.1.unapply_indices_unchecked(indices).into_iter().map(|i| i.set(i.get() + self.0.len_in()))); result } #[inline] fn is_identity(&self) -> bool { - self.map1.is_identity() && self.map2.is_identity() + false } } -struct UniformConcat +#[derive(Debug, Clone, PartialEq)] +pub struct UniformConcat(Array) +where + M: Map, + Array: Deref; + +impl UniformConcat where M: Map, - Array: Deref, + Array: Deref, { - maps: Array, - mod_out: usize, + pub fn new(array: Array) -> Result { + let mut iter = array.iter(); + if let Some(map) = iter.next() { + let dim_out = map.dim_out(); + let dim_in = map.dim_in(); + let len_out = map.len_out(); + for map in iter { + if map.dim_in() != dim_in || map.dim_out() != dim_out { + return Err(Error::DimensionMismatch); + } else if map.len_out() != len_out { + return Err(Error::LengthMismatch); + } + } + Ok(Self(array)) + } else { + Err(Error::Empty) + } + } + pub fn new_unchecked(array: Array) -> Self { + Self(array) + } + pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, M> { + self.0.iter() + } } -//impl UniformConcat { -//where -// M: Map, -// Array: Deref, -//{ -// pub fn new(maps: Array M2, mod_out: usize) -> Result { -// // TODO: assert unempty -// let mod_in1 = map1 -// .apply_mod_out_to_in(mod_out) -// .ok_or(ConcatError::ModulusNotAMultiple)?; -// let mod_in2 = map2 -// .apply_mod_out_to_in(mod_out) -// .ok_or(ConcatError::ModulusNotAMultiple)?; -// if map1.delta_dim() != map2.delta_dim() { -// return Err(ConcatError::DeltaDimsDiffer); -// } -// Ok(Self { -// map1, -// map2, -// mod_out, -// mod_in1, -// mod_in2, -// }) -// } -//} - impl Map for UniformConcat where M: Map, - Array: Deref, + Array: Deref, { #[inline] fn dim_in(&self) -> usize { - self.maps.iter().map(|(map, _)| map.dim_in()).max().unwrap() + self.0.first().unwrap().dim_in() + } + #[inline] + fn dim_out(&self) -> usize { + self.0.first().unwrap().dim_out() } #[inline] fn delta_dim(&self) -> usize { - self.maps.first().unwrap().0.delta_dim() + self.0.first().unwrap().delta_dim() } #[inline] - fn mod_in(&self) -> usize { - self.maps.iter().map(|(_, mod_in)| mod_in).sum() + fn len_in(&self) -> usize { + self.iter().map(|map| map.len_in()).sum() } #[inline] - fn mod_out(&self) -> usize { - self.mod_out + fn len_out(&self) -> usize { + self.0.first().unwrap().len_out() } #[inline] - fn apply_inplace( + fn apply_inplace_unchecked( &self, - index: usize, + mut index: usize, coords: &mut [f64], stride: usize, offset: usize, ) -> usize { - let (i, mut j) = index.div_rem(&self.mod_in()); - for (map, mod_in) in self.maps.iter() { - if j < *mod_in { - return map.apply_inplace(j + i * mod_in, coords, stride, offset); + for map in self.iter() { + if index < map.len_in() { + return map.apply_inplace_unchecked(index, coords, stride, offset); } - j -= mod_in; + index -= map.len_in(); } - unreachable! {} + unreachable!{} } #[inline] - fn apply_index(&self, index: usize) -> usize { - let (i, mut j) = index.div_rem(&self.mod_in()); - for (map, mod_in) in self.maps.iter() { - if j < *mod_in { - return map.apply_index(j + i * mod_in); + fn apply_index_unchecked(&self, mut index: usize) -> usize { + for map in self.iter() { + if index < map.len_in() { + return map.apply_index_unchecked(index); } - j -= mod_in; + index -= map.len_in(); } - unreachable! {} - } - #[inline] - fn unapply_indices(&self, indices: &[T]) -> Vec { - let mut result = Vec::new(); - let mut offset = 0; - for (map, mod_in) in self.maps.iter() { - result.extend( - map.unapply_indices(indices) - .iter() - .map(|i| i.set(i.get() / mod_in * self.mod_in() + offset + i.get() % mod_in)), - ); - offset += mod_in; + unreachable!{} + } + #[inline] + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + let mut iter = self.iter(); + let map = iter.next().unwrap(); + let mut result = map.unapply_indices_unchecked(indices); + let mut offset = map.len_in(); + for map in iter { + result.extend(map.unapply_indices_unchecked(indices).into_iter().map(|i| i.set(i.get() + offset))); + offset += map.len_in(); } result } #[inline] fn is_identity(&self) -> bool { - self.maps.iter().all(|(map, _)| map.is_identity()) + false } } @@ -405,472 +342,64 @@ where mod tests { use super::*; use approx::assert_abs_diff_eq; - use crate::elementary::{Elementary, Identity}; + use crate::elementary::{Elementary, Identity, WithBounds}; use crate::simplex::Simplex::*; use std::iter; use crate::assert_map_apply; - #[test] - fn uniform_composition0() { - let map = UniformComposition(Vec::::new()); - assert_eq!(map.mod_in(), 1); - assert_eq!(map.mod_out(), 1); - assert_eq!(map.dim_in(), 0); - assert_eq!(map.delta_dim(), 0); - assert_map_apply!(map, 0, 0); - assert_map_apply!(map, 1, 1); - } + type ElemsMap = WithBounds>; #[test] fn uniform_composition1() { - let map = UniformComposition(vec![Identity]); - assert_eq!(map.mod_in(), 1); - assert_eq!(map.mod_out(), 1); + let map = UniformComposition(vec![WithBounds::new(Identity, 0, 1).unwrap()]); + assert_eq!(map.len_in(), 1); + assert_eq!(map.len_out(), 1); assert_eq!(map.dim_in(), 0); assert_eq!(map.delta_dim(), 0); assert_map_apply!(map, 0, 0); - assert_map_apply!(map, 1, 1); - } - - #[test] - fn uniform_composition2() { - let map = UniformComposition(vec![ - Elementary::new_take(vec![1, 0], 3), - Elementary::new_transpose(4, 3), - ]); - assert_eq!(map.mod_in(), 8); - assert_eq!(map.mod_out(), 12); - assert_eq!(map.dim_in(), 0); - assert_eq!(map.delta_dim(), 0); - assert_map_apply!(map, 0, 4); - assert_map_apply!(map, 1, 0); - assert_map_apply!(map, 2, 5); - assert_map_apply!(map, 3, 1); - assert_map_apply!(map, 4, 6); - assert_map_apply!(map, 5, 2); - assert_map_apply!(map, 6, 7); - assert_map_apply!(map, 7, 3); - assert_map_apply!(map, 8, 16); } - #[test] - fn uniform_composition3() { - let map = UniformComposition(vec![ - Elementary::new_edges(Line), - Elementary::new_children(Line), - Elementary::new_children(Line), - ]); - assert_eq!(map.mod_in(), 8); - assert_eq!(map.mod_out(), 1); - assert_eq!(map.dim_in(), 0); - assert_eq!(map.delta_dim(), 1); - assert_map_apply!(map, 0, [[]], 0, [[0.25]]); - assert_map_apply!(map, 1, [[]], 0, [[0.00]]); - assert_map_apply!(map, 2, [[]], 0, [[0.50]]); - assert_map_apply!(map, 3, [[]], 0, [[0.25]]); - assert_map_apply!(map, 4, [[]], 0, [[0.75]]); - assert_map_apply!(map, 5, [[]], 0, [[0.50]]); - assert_map_apply!(map, 6, [[]], 0, [[1.00]]); - assert_map_apply!(map, 7, [[]], 0, [[0.75]]); - assert_map_apply!(map, 8, [[]], 1, [[0.25]]); - assert_eq!(map.unapply_indices(&[0]), vec![0, 1, 2, 3, 4, 5, 6, 7]); - } -} - -//#[derive(Debug, Clone, Copy, PartialEq)] -//pub struct ComposeCodomainDomainMismatch; -// -//impl std::error::Error for ComposeCodomainDomainMismatch {} -// -//impl std::fmt::Display for ComposeCodomainDomainMismatch { -// fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { -// write!( -// f, -// "The codomain of the first map doesn't match the domain of the second map," -// ) -// } -//} -// -//#[derive(Debug, Clone, PartialEq)] -//pub struct Composition { -// inner: Inner, -// outer: Outer, -//} -// -//impl Composition { -// pub fn new(inner: Inner, outer: Outer) -> Result { -// if inner.len_out() == outer.len_in() && inner.dim_out() == outer.dim_in() { -// Ok(Self { inner, outer }) -// } else { -// Err(ComposeCodomainDomainMismatch) -// } -// } -//} -// -//impl BoundedMap for Composition { -// fn len_in(&self) -> usize { -// self.inner.len_in() -// } -// fn len_out(&self) -> usize { -// self.outer.len_out() -// } -// fn dim_in(&self) -> usize { -// self.inner.dim_in() -// } -// fn delta_dim(&self) -> usize { -// self.inner.delta_dim() + self.outer.delta_dim() -// } -// fn apply_inplace_unchecked( -// &self, -// index: usize, -// coordinates: &mut [f64], -// stride: usize, -// offset: usize, -// ) -> usize { -// let index = self -// .inner -// .apply_inplace_unchecked(index, coordinates, stride, offset); -// self.outer -// .apply_inplace_unchecked(index, coordinates, stride, offset) -// } -// fn apply_index_unchecked(&self, index: usize) -> usize { -// let index = self.inner.apply_index_unchecked(index); -// self.outer.apply_index_unchecked(index) -// } -// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { -// self.inner.apply_indices_inplace_unchecked(indices); -// self.outer.apply_indices_inplace_unchecked(indices); -// } -// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { -// let indices = self.outer.unapply_indices_unchecked(indices); -// self.inner.unapply_indices_unchecked(&indices) -// } -// fn is_identity(&self) -> bool { -// self.inner.is_identity() && self.outer.is_identity() -// } -//} -// -//impl UnboundedMap for Composition { -// fn mod_in(&self) -> usize { -// update_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).1 -// } -// fn mod_out(&self) -> usize { -// update_mod_out_in(&self.outer, self.inner.mod_out(), self.inner.mod_in()).0 -// } -// fn dim_in(&self) -> usize { -// update_dim_out_in(&self.outer, self.inner.dim_out(), self.inner.dim_in()).0 -// } -// fn delta_dim(&self) -> usize { -// self.inner.delta_dim() + self.outer.delta_dim() -// } -// fn apply_inplace( -// &self, -// index: usize, -// coordinates: &mut [f64], -// stride: usize, -// offset: usize, -// ) -> usize { -// let index = self.inner.apply_inplace(index, coordinates, stride, offset); -// self.outer.apply_inplace(index, coordinates, stride, offset) -// } -// fn apply_index(&self, index: usize) -> usize { -// let index = self.inner.apply_index(index); -// self.outer.apply_index(index) -// } -// fn apply_indices_inplace(&self, indices: &mut [usize]) { -// self.inner.apply_indices_inplace(indices); -// self.outer.apply_indices_inplace(indices); -// } -// fn unapply_indices(&self, indices: &[T]) -> Vec { -// let indices = self.outer.unapply_indices(indices); -// self.inner.unapply_indices(&indices) -// } -// fn is_identity(&self) -> bool { -// self.inner.is_identity() && self.outer.is_identity() -// } -//} -// -//impl AddOffset for Composition { -// fn add_offset(&mut self, offset: usize) { -// self.inner.add_offset(offset); -// self.outer.add_offset(offset); -// } -//} -// -//pub trait Compose: BoundedMap + Sized { -// fn compose( -// self, -// rhs: Rhs, -// ) -> Result, ComposeCodomainDomainMismatch> { -// Composition::new(self, rhs) -// } -//} -// -//impl Compose for M {} -// -//#[derive(Debug, Clone, PartialEq)] -//pub struct Concat(Vec); -// -//impl Concat { -// pub fn new(items: Vec) -> Self { -// // TODO: Return `Result`. -// let first = items.first().unwrap(); -// let dim_in = first.dim_in(); -// let delta_dim = first.delta_dim(); -// let len_out = first.len_out(); -// for item in items.iter() { -// assert_eq!(item.dim_in(), dim_in); -// assert_eq!(item.delta_dim(), delta_dim); -// assert_eq!(item.len_out(), len_out); -// } -// Self(items) -// } -// fn resolve_item_unchecked(&self, mut index: usize) -> (&Item, usize) { -// for item in self.0.iter() { -// if index < item.len_in() { -// return (item, index); -// } -// index -= item.len_in(); -// } -// panic!("index out of range"); -// } -// pub fn iter(&self) -> impl Iterator { -// self.0.iter() -// } -// pub fn into_vec(self) -> Vec { -// self.0 -// } -//} -// -//impl BoundedMap for Concat { -// fn dim_in(&self) -> usize { -// self.0.first().unwrap().dim_in() -// } -// fn delta_dim(&self) -> usize { -// self.0.first().unwrap().delta_dim() -// } -// fn len_out(&self) -> usize { -// self.0.first().unwrap().len_out() -// } -// fn len_in(&self) -> usize { -// self.iter().map(|item| item.len_in()).sum() -// } -// fn apply_inplace_unchecked( -// &self, -// index: usize, -// coordinates: &mut [f64], -// stride: usize, -// offset: usize, -// ) -> usize { -// let (item, index) = self.resolve_item_unchecked(index); -// item.apply_inplace_unchecked(index, coordinates, stride, offset) -// } -// fn apply_index_unchecked(&self, index: usize) -> usize { -// let (item, index) = self.resolve_item_unchecked(index); -// item.apply_index_unchecked(index) -// } -// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { -// let mut result = Vec::new(); -// let mut offset = 0; -// for item in &self.0 { -// result.extend( -// item.unapply_indices_unchecked(indices) -// .into_iter() -// .map(|i| i.set(i.get() + offset)), -// ); -// offset += item.len_in(); -// } -// result -// } -// fn is_identity(&self) -> bool { -// false -// } -//} -// -//impl AddOffset for Concat { -// fn add_offset(&mut self, offset: usize) { -// for item in &mut self.0 { -// item.add_offset(offset); -// } -// } -//} -// -//impl PushElementary for Concat -//where -// M: BoundedMap + PushElementary, -//{ -// fn push_elementary(&mut self, map: &Elementary) { -// match map { -// Elementary::Children(_) | Elementary::Edges(_) => { -// for item in &mut self.0 { -// item.push_elementary(map); -// } -// } -// Elementary::Take(take) => { -// let mut offset = 0; -// let indices = take.get_indices(); -// let mut indices = indices.iter().cloned().peekable(); -// for item in &mut self.0 { -// let len = item.len_in(); -// let mut item_indices = Vec::new(); -// while let Some(index) = indices.next_if(|&i| i < offset + len) { -// if index < offset { -// unimplemented! {"take of concatenation with unordered indices"}; -// } -// item_indices.push(index - offset); -// } -// item.push_elementary(&Elementary::new_take(item_indices, len)); -// offset += len; -// } -// } -// _ => unimplemented! {}, -// } -// } -//} -// -//struct Product(M0, M1); -// -//impl BoundedMap for Product { -// fn dim_in(&self) -> usize { -// self.0.dim_in() + self.1.dim_in() -// } -// fn delta_dim(&self) -> usize { -// self.0.delta_dim() + self.1.delta_dim() -// } -// fn len_out(&self) -> usize { -// self.0.len_out() * self.1.len_out() -// } -// fn len_in(&self) -> usize { -// self.0.len_in() * self.1.len_in() +// #[test] +// fn uniform_composition2() { +// let map = UniformComposition(vec![ +// Elementary::new_take(vec![1, 0], 3), +// Elementary::new_transpose(4, 3), +// ]); +// assert_eq!(map.mod_in(), 8); +// assert_eq!(map.mod_out(), 12); +// assert_eq!(map.dim_in(), 0); +// assert_eq!(map.delta_dim(), 0); +// assert_map_apply!(map, 0, 4); +// assert_map_apply!(map, 1, 0); +// assert_map_apply!(map, 2, 5); +// assert_map_apply!(map, 3, 1); +// assert_map_apply!(map, 4, 6); +// assert_map_apply!(map, 5, 2); +// assert_map_apply!(map, 6, 7); +// assert_map_apply!(map, 7, 3); +// assert_map_apply!(map, 8, 16); // } -// fn apply_inplace_unchecked( -// &self, -// index: usize, -// coords: &mut [f64], -// stride: usize, -// offset: usize, -// ) -> usize { -// let index = self -// .1 -// .apply_inplace_unchecked(index, coords, stride, offset + self.0.dim_in()); -// let index = Transpose::new(self.0.len_in(), self.1.len_in()).apply_index(index); -// let index = self -// .0 -// .apply_inplace_unchecked(index, coords, stride, offset); -// Transpose::new(self.1.len_in(), self.0.len_in()).apply_index(index) -// } -// fn apply_index_unchecked(&self, index: usize) -> usize { -// let index = self.1.apply_index_unchecked(index); -// let index = Transpose::new(self.0.len_in(), self.1.len_in()).apply_index(index); -// let index = self.0.apply_index_unchecked(index); -// Transpose::new(self.1.len_in(), self.0.len_in()).apply_index(index) -// } -// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { -// self.1.apply_indices_inplace_unchecked(indices); -// Transpose::new(self.0.len_in(), self.1.len_in()).apply_indices_inplace(indices); -// self.0.apply_indices_inplace_unchecked(indices); -// Transpose::new(self.1.len_in(), self.0.len_in()).apply_indices_inplace(indices); -// } -// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { -// let indices = -// Transpose::new(self.1.len_in(), self.0.len_in()).unapply_indices(indices); -// let indices = self.0.unapply_indices_unchecked(&indices); -// let indices = -// Transpose::new(self.0.len_in(), self.1.len_in()).unapply_indices(&indices); -// self.1.unapply_indices_unchecked(&indices) -// } -// fn is_identity(&self) -> bool { -// self.0.is_identity() && self.1.is_identity() -// } -//} -// -//#[inline] -//fn update_dim_out_in(map: &M, dim_out: usize, dim_in: usize) -> (usize, usize) { -// if let Some(n) = map.dim_in().checked_sub(dim_out) { -// (map.dim_out(), dim_in + n) -// } else { -// (dim_out, dim_in) -// } -//} -// -//#[inline] -//fn update_mod_out_in(map: &M, mod_out: usize, mod_in: usize) -> (usize, usize) { -// let n = mod_out.lcm(&map.mod_in()); -// (n / map.mod_in() * map.mod_out(), mod_in * n / mod_out) -//} -// -///// Composition. -//impl UnboundedMap for Array -//where -// Item: UnboundedMap, -// Array: Deref + DerefMut + std::fmt::Debug, -//{ -// fn dim_in(&self) -> usize { -// self.deref() -// .iter() -// .rev() -// .fold((0, 0), |(dim_out, dim_in), item| { -// update_dim_out_in(item, dim_out, dim_in) -// }) -// .1 -// } -// fn delta_dim(&self) -> usize { -// self.iter().map(|item| item.delta_dim()).sum() -// } -// fn mod_in(&self) -> usize { -// self.deref() -// .iter() -// .rev() -// .fold((1, 1), |(mod_out, mod_in), item| { -// update_mod_out_in(item, mod_out, mod_in) -// }) -// .1 -// } -// fn mod_out(&self) -> usize { -// self.deref() -// .iter() -// .rev() -// .fold((1, 1), |(mod_out, mod_in), item| { -// update_mod_out_in(item, mod_out, mod_in) -// }) -// .0 -// } -// fn apply_inplace( -// &self, -// index: usize, -// coordinates: &mut [f64], -// stride: usize, -// offset: usize, -// ) -> usize { -// self.iter().rev().fold(index, |index, item| { -// item.apply_inplace(index, coordinates, stride, offset) -// }) -// } -// fn apply_index(&self, index: usize) -> usize { -// self.iter() -// .rev() -// .fold(index, |index, item| item.apply_index(index)) -// } -// fn apply_indices_inplace(&self, indices: &mut [usize]) { -// for item in self.iter().rev() { -// item.apply_indices_inplace(indices); -// } -// } -// fn unapply_indices(&self, indices: &[T]) -> Vec { -// self.iter().fold(indices.to_vec(), |indices, item| { -// item.unapply_indices(&indices) -// }) -// } -//} // -//impl AddOffset for Array -//where -// Item: AddOffset, -// Array: Deref + DerefMut + std::fmt::Debug, -//{ -// fn add_offset(&mut self, offset: usize) { -// for item in self.iter_mut() { -// item.add_offset(offset); -// } +// #[test] +// fn uniform_composition3() { +// let map = UniformComposition(vec![ +// Elementary::new_edges(Line), +// Elementary::new_children(Line), +// Elementary::new_children(Line), +// ]); +// assert_eq!(map.mod_in(), 8); +// assert_eq!(map.mod_out(), 1); +// assert_eq!(map.dim_in(), 0); +// assert_eq!(map.delta_dim(), 1); +// assert_map_apply!(map, 0, [[]], 0, [[0.25]]); +// assert_map_apply!(map, 1, [[]], 0, [[0.00]]); +// assert_map_apply!(map, 2, [[]], 0, [[0.50]]); +// assert_map_apply!(map, 3, [[]], 0, [[0.25]]); +// assert_map_apply!(map, 4, [[]], 0, [[0.75]]); +// assert_map_apply!(map, 5, [[]], 0, [[0.50]]); +// assert_map_apply!(map, 6, [[]], 0, [[1.00]]); +// assert_map_apply!(map, 7, [[]], 0, [[0.75]]); +// assert_map_apply!(map, 8, [[]], 1, [[0.25]]); +// assert_eq!(map.unapply_indices(&[0]), vec![0, 1, 2, 3, 4, 5, 6, 7]); // } -//} +} diff --git a/src/tesselation.rs b/src/tesselation.rs index d7dc1a199..a57f9a32d 100644 --- a/src/tesselation.rs +++ b/src/tesselation.rs @@ -3,7 +3,43 @@ use crate::ops::{Concatenation, WithBoundsError}; use crate::simplex::Simplex; use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; +struct UniformTesselation { + shapes: Vec, + delta_dim: usize, + len_in: usize, + len_out: usize, + map: Vec, +} + struct Tesselation { + maps: Vec, + reorder: Option>, +} + + + + + +enum Tesselation { + Uniform(UniformTesselation), + Concatenation(Vec), + Product(Vec), + Reordered(Box, Vec), +} + + +impl RelativeTo for Tesselation { + fn relative_to(&self, target: &Tesselation) -> ... { + // convert product + } +} + + + + + + +struct UniformTesselation { shapes: Vec delta_dim: usize, len_out: usize, From d79f4644fe31f5db6346533cf04b30c6cf3ec87e Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 29 Jun 2022 14:51:59 +0200 Subject: [PATCH 25/45] wip --- src/elementary.rs | 26 ++- src/lib.rs | 16 +- src/ops.rs | 481 +++++++++++++++++++++++++++++++++++++++------ src/tesselation.rs | 19 +- 4 files changed, 466 insertions(+), 76 deletions(-) diff --git a/src/elementary.rs b/src/elementary.rs index f558b4df5..8014cdbbd 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -1,6 +1,6 @@ use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; -use crate::{Map, Error, AddOffset, UnapplyIndicesData}; +use crate::{AddOffset, Error, Map, UnapplyIndicesData}; use num::Integer as _; use std::ops::{Deref, DerefMut}; use std::rc::Rc; @@ -138,8 +138,7 @@ impl UnboundedMap for Offset { stride: usize, offset: usize, ) -> usize { - self.0 - .apply_inplace(index, coords, stride, offset + self.1) + self.0.apply_inplace(index, coords, stride, offset + self.1) } #[inline] fn apply_index(&self, index: usize) -> usize { @@ -855,7 +854,7 @@ pub struct WithBounds { } impl WithBounds { - pub fn new(map: M, dim_in: usize, len_in: usize) -> Result { + pub fn from_input(map: M, dim_in: usize, len_in: usize) -> Result { if dim_in < map.dim_in() { Err(Error::DimensionMismatch) } else if len_in % map.mod_in() != 0 { @@ -864,10 +863,27 @@ impl WithBounds { Ok(Self::new_unchecked(map, dim_in, len_in)) } } + pub fn from_output(map: M, dim_out: usize, len_out: usize) -> Result { + if dim_out < map.dim_out() { + Err(Error::DimensionMismatch) + } else if len_out % map.mod_out() != 0 { + Err(Error::LengthMismatch) + } else { + let dim_in = dim_out - map.delta_dim(); + let len_in = len_out / map.mod_out() * map.mod_in(); + Ok(Self::new_unchecked(map, dim_in, len_in)) + } + } pub fn new_unchecked(map: M, dim_in: usize, len_in: usize) -> Self { let delta_dim = map.delta_dim(); let len_out = len_in / map.mod_in() * map.mod_out(); - Self { map, dim_in, delta_dim, len_in, len_out } + Self { + map, + dim_in, + delta_dim, + len_in, + len_out, + } } } diff --git a/src/lib.rs b/src/lib.rs index 3684af22d..cc3199358 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,6 @@ impl std::fmt::Display for Error { } } - pub trait Map { fn len_out(&self) -> usize; fn len_in(&self) -> usize; @@ -91,7 +90,7 @@ pub trait AddOffset { fn add_offset(&mut self, offset: usize); } -pub trait UnapplyIndicesData: Clone + std::fmt::Debug { +pub trait UnapplyIndicesData: Clone { fn get(&self) -> usize; fn set(&self, index: usize) -> Self; } @@ -114,7 +113,7 @@ macro_rules! assert_map_apply { let item = $item.borrow(); let incoords = $incoords; let outcoords = $outcoords; - assert_eq!(incoords.len(), outcoords.len()); + assert_eq!(incoords.len(), outcoords.len(), "incoords outcoords"); let stride; let mut work: Vec<_>; if incoords.len() == 0 { @@ -127,7 +126,12 @@ macro_rules! assert_map_apply { work[..incoord.len()].copy_from_slice(incoord); } } - assert_eq!(item.apply_inplace($inidx, &mut work, stride, 0), $outidx).unwrap(); + assert_eq!( + item.apply_inplace($inidx, &mut work, stride, 0), + Some($outidx), + "apply_inplace", + ); + assert_eq!(item.apply_index($inidx), Some($outidx), "apply_index"); for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { assert_abs_diff_eq!(actual[..], desired[..]); } @@ -137,8 +141,10 @@ macro_rules! assert_map_apply { let item = $item.borrow(); let mut work = Vec::with_capacity(0); assert_eq!( - item.apply_inplace($inidx, &mut work, item.dim_out(), 0).unwrap(), + item.apply_inplace($inidx, &mut work, item.dim_out(), 0) + .unwrap(), $outidx ); + assert_eq!(item.apply_index($inidx), Some($outidx)); }}; } diff --git a/src/ops.rs b/src/ops.rs index dcb215b37..5126d4c3a 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,5 +1,6 @@ use crate::{Error, Map, UnapplyIndicesData}; use std::ops::Deref; +use num::Integer as _; #[derive(Debug, Clone, PartialEq)] pub struct BinaryComposition(Inner, Outer); @@ -41,12 +42,16 @@ impl Map for BinaryComposition { stride: usize, offset: usize, ) -> usize { - let index = self.0.apply_inplace_unchecked(index, coords, stride, offset); - self.1.apply_inplace_unchecked(index, coords, stride, offset) + let index = self + .0 + .apply_inplace_unchecked(index, coords, stride, offset); + self.1 + .apply_inplace_unchecked(index, coords, stride, offset) } #[inline] fn apply_index_unchecked(&self, index: usize) -> usize { - self.1.apply_index_unchecked(self.0.apply_index_unchecked(index)) + self.1 + .apply_index_unchecked(self.0.apply_index_unchecked(index)) } #[inline] fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { @@ -55,7 +60,8 @@ impl Map for BinaryComposition { } #[inline] fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - self.0.unapply_indices_unchecked(&self.1.unapply_indices_unchecked(indices)) + self.0 + .unapply_indices_unchecked(&self.1.unapply_indices_unchecked(indices)) } #[inline] fn is_identity(&self) -> bool { @@ -140,7 +146,8 @@ where } #[inline] fn apply_index_unchecked(&self, index: usize) -> usize { - self.iter().fold(index, |index, map| map.apply_index_unchecked(index)) + self.iter() + .fold(index, |index, map| map.apply_index_unchecked(index)) } #[inline] fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { @@ -151,7 +158,9 @@ where fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { let mut iter = self.iter().rev(); let indices = iter.next().unwrap().unapply_indices_unchecked(indices); - iter.fold(indices, |indices, map| map.unapply_indices_unchecked(&indices)) + iter.fold(indices, |indices, map| { + map.unapply_indices_unchecked(&indices) + }) } #[inline] fn is_identity(&self) -> bool { @@ -207,9 +216,11 @@ impl Map for BinaryConcat { offset: usize, ) -> usize { if index < self.0.len_in() { - self.0.apply_inplace_unchecked(index, coords, stride, offset) + self.0 + .apply_inplace_unchecked(index, coords, stride, offset) } else { - self.1.apply_inplace_unchecked(index - self.0.len_in(), coords, stride, offset) + self.1 + .apply_inplace_unchecked(index - self.0.len_in(), coords, stride, offset) } } #[inline] @@ -223,7 +234,12 @@ impl Map for BinaryConcat { #[inline] fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { let mut result = self.0.unapply_indices_unchecked(indices); - result.extend(self.1.unapply_indices_unchecked(indices).into_iter().map(|i| i.set(i.get() + self.0.len_in()))); + result.extend( + self.1 + .unapply_indices_unchecked(indices) + .into_iter() + .map(|i| i.set(i.get() + self.0.len_in())), + ); result } #[inline] @@ -308,7 +324,7 @@ where } index -= map.len_in(); } - unreachable!{} + unreachable! {} } #[inline] fn apply_index_unchecked(&self, mut index: usize) -> usize { @@ -318,7 +334,7 @@ where } index -= map.len_in(); } - unreachable!{} + unreachable! {} } #[inline] fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { @@ -327,7 +343,11 @@ where let mut result = map.unapply_indices_unchecked(indices); let mut offset = map.len_in(); for map in iter { - result.extend(map.unapply_indices_unchecked(indices).into_iter().map(|i| i.set(i.get() + offset))); + result.extend( + map.unapply_indices_unchecked(indices) + .into_iter() + .map(|i| i.set(i.get() + offset)), + ); offset += map.len_in(); } result @@ -338,68 +358,407 @@ where } } +#[derive(Debug, Clone, PartialEq)] +pub struct BinaryProduct(M0, M1); + +impl BinaryProduct { + pub fn new(map0: M0, map1: M1) -> Self { + Self(map0, map1) + } +} + +impl Map for BinaryProduct { + #[inline] + fn dim_in(&self) -> usize { + self.0.dim_in() + self.1.dim_in() + } + #[inline] + fn dim_out(&self) -> usize { + self.0.dim_out() + self.0.dim_out() + } + #[inline] + fn delta_dim(&self) -> usize { + self.0.delta_dim() + self.1.delta_dim() + } + #[inline] + fn len_in(&self) -> usize { + self.0.len_in() * self.1.len_in() + } + #[inline] + fn len_out(&self) -> usize { + self.0.len_out() * self.1.len_out() + } + #[inline] + fn apply_inplace_unchecked( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { + let (index0, index1) = index.div_rem(&self.0.len_in()); + let index0 = self + .0 + .apply_inplace_unchecked(index0, coords, stride, offset); + let index1 = + self.1 + .apply_inplace_unchecked(index1, coords, stride, offset + self.0.dim_out()); + index0 * self.0.len_out() + index1 + } + #[inline] + fn apply_index_unchecked(&self, index: usize) -> usize { + let (index0, index1) = index.div_rem(&self.0.len_in()); + let index0 = self.0.apply_index_unchecked(index0); + let index1 = self.1.apply_index_unchecked(index1); + index0 * self.0.len_out() + index1 + } + #[inline] + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + let indices: Vec<_> = indices + .iter() + .map(|k| { + let (i, j) = k.get().div_rem(&self.0.len_in()); + UnapplyBinaryProduct(i, j, k.clone()) + }) + .collect(); + let mut indices = self.0.unapply_indices_unchecked(&indices); + indices + .iter_mut() + .for_each(|UnapplyBinaryProduct(ref mut i, ref mut j, _)| std::mem::swap(i, j)); + let indices = self.1.unapply_indices_unchecked(&indices); + indices + .into_iter() + .map(|UnapplyBinaryProduct(i, j, k)| k.set(j * self.0.len_out() + i)) + .collect() + } + #[inline] + fn is_identity(&self) -> bool { + self.0.is_identity() && self.1.is_identity() + } +} + +#[derive(Debug, Clone)] +struct UnapplyBinaryProduct(usize, usize, T); + +impl UnapplyIndicesData for UnapplyBinaryProduct { + fn get(&self) -> usize { + self.0 + } + fn set(&self, index: usize) -> Self { + Self(index, self.1, self.2.clone()) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct UniformProduct(Array) +where + M: Map, + Array: Deref; + +impl UniformProduct +where + M: Map, + Array: Deref, +{ + pub fn new(array: Array) -> Self { + Self(array) + } + pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, M> { + self.0.iter() + } + fn unravel_index(&self, index: usize) -> Vec { + let mut indices: Vec<_> = self.iter().rev().scan(index, |index, map| { + let (i0, i1) = index.div_rem(&map.len_in()); + *index = i0; + Some(i1) + }).collect(); + indices.reverse(); + indices + } +} + +//impl Map for UniformProduct +//where +// M: Map, +// Array: Deref, +//{ +// #[inline] +// fn dim_in(&self) -> usize { +// self.iter().map(|map| map.dim_in()).sum() +// } +// #[inline] +// fn dim_out(&self) -> usize { +// self.iter().map(|map| map.dim_out()).sum() +// } +// #[inline] +// fn delta_dim(&self) -> usize { +// self.iter().map(|map| map.delta_dim()).sum() +// } +// #[inline] +// fn len_in(&self) -> usize { +// self.iter().map(|map| map.len_in()).product() +// } +// #[inline] +// fn len_out(&self) -> usize { +// self.iter().map(|map| map.len_out()).product() +// } +// #[inline] +// fn apply_inplace_unchecked( +// &self, +// index: usize, +// coords: &mut [f64], +// stride: usize, +// mut offset: usize, +// ) -> usize { +// let mut iout = 0; +// for (map, iin) in self.iter().zip(self.unravel_index(index)) { +// iout = iout * map.len_out() + self.apply_inplace_unchecked(iin, coords, stride, offset); +// offset += map.dim_out(); +// } +// out_index +// } +// #[inline] +// fn apply_index_unchecked(&self, mut index: usize) -> usize { +// let mut out_index = 0; +// for (map, iin) in self.iter().zip(self.unravel_index(index)) { +// iout = iout * map.len_out() + self.apply_index_unchecked(iin); +// } +// iout +// } +// #[inline] +// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { +// let indices: Vec<_> = indices +// .iter() +// .map(|k| { +// let (i, j) = k.get().div_rem(&self.0.len_in()); +// UnapplyBinaryProduct(i, j, k.clone()) +// }) +// .collect(); +// +// +// let mut iter = self.iter(); +// let map = iter.next().unwrap(); +// let mut result = map.unapply_indices_unchecked(indices); +// let mut offset = map.len_in(); +// for map in iter { +// result.extend( +// map.unapply_indices_unchecked(indices) +// .into_iter() +// .map(|i| i.set(i.get() + offset)), +// ); +// offset += map.len_in(); +// } +// result +// } +// #[inline] +// fn is_identity(&self) -> bool { +// false +// } +//} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* $(,)? + ) $(-> $ret:ty)? + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { + if $self.0.deref().len() == 1 { + $self.0.deref()[0].$fn($($arg),*) + //} else if $self.0.deref().len() == 2 { + // BinaryProduct(&$self.0.deref()[0], $self.0.deref()[1]).$fn($($arg),*) + } else { + BinaryProduct(UniformProduct(&$self.0.deref()[..1]), UniformProduct(&$self.0.deref()[1..])).$fn($($arg),*) + } + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { + if $self.0.deref().len() == 1 { + $self.0.deref_mut()[0].$fn($($arg),*) + //} else if $self.0.deref().len() == 2 { + // BinaryProduct(&$self.0.deref_mut()[0], &$self.0.deref_mut()[1]).$fn($($arg),*) + } else { + BinaryProduct(UniformProduct(&$self.0.deref_mut()[..1]), UniformProduct(&$self.0.deref_mut()[1..])).$fn($($arg),*) + } + } + }; +} + +impl Map for UniformProduct +where + M: Map, + Array: Deref, +{ + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn apply_inplace_unchecked( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> usize} + dispatch! {fn apply_inplace( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> Option} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} +} + +// pub struct OptionReorder(map, Option); + #[cfg(test)] mod tests { use super::*; - use approx::assert_abs_diff_eq; - use crate::elementary::{Elementary, Identity, WithBounds}; + use crate::assert_map_apply; + use crate::elementary::{Elementary, WithBounds}; use crate::simplex::Simplex::*; + use approx::assert_abs_diff_eq; use std::iter; - use crate::assert_map_apply; - type ElemsMap = WithBounds>; + macro_rules! elementaries { + (Point*$len_out:literal $($tail:tt)*) => {{ + let mut comp: Vec = Vec::new(); + elementaries!{@push comp, Point; $($tail)*} + comp.reverse(); + WithBounds::from_output(comp, 0, $len_out).unwrap() + }}; + ($simplex:ident*$len_out:literal $($tail:tt)*) => {{ + let mut comp = Vec::new(); + elementaries!{@push comp, $simplex; $($tail)*} + comp.reverse(); + WithBounds::from_output(comp, $simplex.dim(), $len_out).unwrap() + }}; + (@push $comp:ident, $simplex:expr;) => {}; + (@push $comp:ident, $simplex:expr; <- Children $($tail:tt)*) => {{ + $comp.push(Elementary::new_children($simplex)); + elementaries!{@push $comp, $simplex; $($tail)*} + }}; + (@push $comp:ident, $simplex:expr; <- Edges $($tail:tt)*) => {{ + $comp.push(Elementary::new_edges($simplex)); + elementaries!{@push $comp, $simplex.edge_simplex(); $($tail)*} + }}; + (@push $comp:ident, $simplex:expr; <- Transpose($len1:expr, $len2:expr) $($tail:tt)*) => {{ + $comp.push(Elementary::new_transpose($len1, $len2)); + elementaries!{@push $comp, $simplex; $($tail)*} + }}; + (@push $comp:ident, $simplex:expr; <- Take($indices:expr, $len:expr) $($tail:tt)*) => {{ + $comp.push(Elementary::new_take($indices.to_vec(), $len)); + elementaries!{@push $comp, $simplex; $($tail)*} + }}; + } #[test] fn uniform_composition1() { - let map = UniformComposition(vec![WithBounds::new(Identity, 0, 1).unwrap()]); + let map = UniformComposition::new(vec![elementaries![Point * 1]]).unwrap(); assert_eq!(map.len_in(), 1); assert_eq!(map.len_out(), 1); assert_eq!(map.dim_in(), 0); assert_eq!(map.delta_dim(), 0); - assert_map_apply!(map, 0, 0); + assert_eq!(map.apply_index(0), Some(0)); + assert_eq!(map.apply_index(1), None); } -// #[test] -// fn uniform_composition2() { -// let map = UniformComposition(vec![ -// Elementary::new_take(vec![1, 0], 3), -// Elementary::new_transpose(4, 3), -// ]); -// assert_eq!(map.mod_in(), 8); -// assert_eq!(map.mod_out(), 12); -// assert_eq!(map.dim_in(), 0); -// assert_eq!(map.delta_dim(), 0); -// assert_map_apply!(map, 0, 4); -// assert_map_apply!(map, 1, 0); -// assert_map_apply!(map, 2, 5); -// assert_map_apply!(map, 3, 1); -// assert_map_apply!(map, 4, 6); -// assert_map_apply!(map, 5, 2); -// assert_map_apply!(map, 6, 7); -// assert_map_apply!(map, 7, 3); -// assert_map_apply!(map, 8, 16); -// } -// -// #[test] -// fn uniform_composition3() { -// let map = UniformComposition(vec![ -// Elementary::new_edges(Line), -// Elementary::new_children(Line), -// Elementary::new_children(Line), -// ]); -// assert_eq!(map.mod_in(), 8); -// assert_eq!(map.mod_out(), 1); -// assert_eq!(map.dim_in(), 0); -// assert_eq!(map.delta_dim(), 1); -// assert_map_apply!(map, 0, [[]], 0, [[0.25]]); -// assert_map_apply!(map, 1, [[]], 0, [[0.00]]); -// assert_map_apply!(map, 2, [[]], 0, [[0.50]]); -// assert_map_apply!(map, 3, [[]], 0, [[0.25]]); -// assert_map_apply!(map, 4, [[]], 0, [[0.75]]); -// assert_map_apply!(map, 5, [[]], 0, [[0.50]]); -// assert_map_apply!(map, 6, [[]], 0, [[1.00]]); -// assert_map_apply!(map, 7, [[]], 0, [[0.75]]); -// assert_map_apply!(map, 8, [[]], 1, [[0.25]]); -// assert_eq!(map.unapply_indices(&[0]), vec![0, 1, 2, 3, 4, 5, 6, 7]); -// } + #[test] + fn uniform_composition2() { + let map = UniformComposition::new(vec![ + elementaries![Point*12 <- Take([1, 0], 3)], + elementaries![Point*12 <- Transpose(4, 3)], + ]) + .unwrap(); + assert_eq!(map.len_in(), 8); + assert_eq!(map.len_out(), 12); + assert_eq!(map.dim_in(), 0); + assert_eq!(map.delta_dim(), 0); + assert_eq!(map.apply_index(0), Some(4)); + assert_eq!(map.apply_index(1), Some(0)); + assert_eq!(map.apply_index(2), Some(5)); + assert_eq!(map.apply_index(3), Some(1)); + assert_eq!(map.apply_index(4), Some(6)); + assert_eq!(map.apply_index(5), Some(2)); + assert_eq!(map.apply_index(6), Some(7)); + assert_eq!(map.apply_index(7), Some(3)); + assert_eq!(map.apply_index(8), None); + } + + #[test] + fn uniform_composition3() { + let map = UniformComposition::new(vec![ + elementaries![Line*4 <- Edges], + elementaries![Line*2 <- Children], + elementaries![Line*1 <- Children], + ]) + .unwrap(); + assert_eq!(map.len_in(), 8); + assert_eq!(map.len_out(), 1); + assert_eq!(map.dim_in(), 0); + assert_eq!(map.delta_dim(), 1); + assert_map_apply!(map, 0, [[]], 0, [[0.25]]); + assert_map_apply!(map, 1, [[]], 0, [[0.00]]); + assert_map_apply!(map, 2, [[]], 0, [[0.50]]); + assert_map_apply!(map, 3, [[]], 0, [[0.25]]); + assert_map_apply!(map, 4, [[]], 0, [[0.75]]); + assert_map_apply!(map, 5, [[]], 0, [[0.50]]); + assert_map_apply!(map, 6, [[]], 0, [[1.00]]); + assert_map_apply!(map, 7, [[]], 0, [[0.75]]); + assert_eq!(map.apply_index(8), None); + assert_eq!( + map.unapply_indices(&[0]), + Some(vec![0, 1, 2, 3, 4, 5, 6, 7]) + ); + } + + #[test] + fn uniform_concat() { + let map = UniformConcat::new(vec![ + elementaries![Line*3 <- Take([0], 3)], + elementaries![Line*3 <- Take([1], 3) <- Children], + elementaries![Line*3 <- Take([2], 3) <- Children <- Children], + ]) + .unwrap(); + assert_eq!(map.len_out(), 3); + assert_eq!(map.len_in(), 7); + assert_eq!(map.dim_in(), 1); + assert_eq!(map.dim_out(), 1); + assert_map_apply!(map, 0, [[0.0], [1.0]], 0, [[0.00], [1.00]]); + assert_map_apply!(map, 1, [[0.0], [1.0]], 1, [[0.00], [0.50]]); + assert_map_apply!(map, 2, [[0.0], [1.0]], 1, [[0.50], [1.00]]); + assert_map_apply!(map, 3, [[0.0], [1.0]], 2, [[0.00], [0.25]]); + assert_map_apply!(map, 4, [[0.0], [1.0]], 2, [[0.25], [0.50]]); + assert_map_apply!(map, 5, [[0.0], [1.0]], 2, [[0.50], [0.75]]); + assert_map_apply!(map, 6, [[0.0], [1.0]], 2, [[0.75], [1.00]]); + assert_eq!(map.apply_index(7), None); + assert_eq!(map.unapply_indices(&[0, 2]), Some(vec![0, 3, 4, 5, 6])); + } + + #[test] + fn uniform_product1() { + let map = UniformProduct::new(vec![ + elementaries![Line*1 <- Edges], + elementaries![Line*1 <- Children], + ]); + assert_eq!(map.len_out(), 1); + assert_eq!(map.len_in(), 4); + assert_eq!(map.dim_in(), 1); + assert_eq!(map.dim_out(), 2); + assert_map_apply!(map, 0, [[0.0], [1.0]], 0, [[1.0, 0.0], [1.0, 0.5]]); + assert_map_apply!(map, 1, [[0.0], [1.0]], 0, [[1.0, 0.5], [1.0, 1.0]]); + assert_map_apply!(map, 2, [[0.0], [1.0]], 0, [[0.0, 0.0], [0.0, 0.5]]); + assert_map_apply!(map, 3, [[0.0], [1.0]], 0, [[0.0, 0.5], [0.0, 1.0]]); + } } diff --git a/src/tesselation.rs b/src/tesselation.rs index a57f9a32d..f37964d9b 100644 --- a/src/tesselation.rs +++ b/src/tesselation.rs @@ -5,18 +5,27 @@ use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; struct UniformTesselation { shapes: Vec, - delta_dim: usize, - len_in: usize, - len_out: usize, - map: Vec, + map: WithBounds>, +} + +impl Deref for UniformTesselation { + type Target = WithBounds>; + + fn deref(&self) -> Self::Target { + self.map + } } struct Tesselation { - maps: Vec, + maps: UniformConcat, reorder: Option>, } +type Tesselation = OptionReorder>, Vec>; + + + From ab464ad00bf8f6d591b844fa59f035e460778dd2 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 29 Jun 2022 16:28:48 +0200 Subject: [PATCH 26/45] WIP --- src/elementary.rs | 9 +---- src/lib.rs | 2 +- src/ops.rs | 95 ++++++++++++++++++++++++++--------------------- 3 files changed, 55 insertions(+), 51 deletions(-) diff --git a/src/elementary.rs b/src/elementary.rs index 8014cdbbd..7dc1e4d1a 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -819,14 +819,7 @@ where } #[inline] fn unapply_indices(&self, indices: &[T]) -> Vec { - let mut iter = self.iter().rev(); - if let Some(map) = iter.next() { - iter.fold(map.unapply_indices(indices), |indices, map| { - map.unapply_indices(&indices) - }) - } else { - Vec::new() - } + self.iter().rev().fold(indices.to_vec(), |indices, map| map.unapply_indices(&indices)) } #[inline] fn is_identity(&self) -> bool { diff --git a/src/lib.rs b/src/lib.rs index cc3199358..7bbbb2ff1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ pub trait AddOffset { fn add_offset(&mut self, offset: usize); } -pub trait UnapplyIndicesData: Clone { +pub trait UnapplyIndicesData: Clone + std::fmt::Debug { fn get(&self) -> usize; fn set(&self, index: usize) -> Self; } diff --git a/src/ops.rs b/src/ops.rs index 5126d4c3a..3742df5f4 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -396,40 +396,43 @@ impl Map for BinaryProduct { stride: usize, offset: usize, ) -> usize { - let (index0, index1) = index.div_rem(&self.0.len_in()); + let (index0, index1) = index.div_rem(&self.1.len_in()); let index0 = self .0 .apply_inplace_unchecked(index0, coords, stride, offset); let index1 = self.1 .apply_inplace_unchecked(index1, coords, stride, offset + self.0.dim_out()); - index0 * self.0.len_out() + index1 + index0 * self.1.len_out() + index1 } #[inline] fn apply_index_unchecked(&self, index: usize) -> usize { - let (index0, index1) = index.div_rem(&self.0.len_in()); + let (index0, index1) = index.div_rem(&self.1.len_in()); let index0 = self.0.apply_index_unchecked(index0); let index1 = self.1.apply_index_unchecked(index1); - index0 * self.0.len_out() + index1 + index0 * self.1.len_out() + index1 } #[inline] fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - let indices: Vec<_> = indices + // TODO: collect unique indices per map, unapply and merge + let idx: Vec<_> = indices .iter() - .map(|k| { - let (i, j) = k.get().div_rem(&self.0.len_in()); - UnapplyBinaryProduct(i, j, k.clone()) + .enumerate() + .map(|(i, j)| { + let (j, k) = j.get().div_rem(&self.1.len_out()); + UnapplyBinaryProduct(i, k, j) }) .collect(); - let mut indices = self.0.unapply_indices_unchecked(&indices); - indices + let mut idx = self.0.unapply_indices_unchecked(&idx); + idx .iter_mut() - .for_each(|UnapplyBinaryProduct(ref mut i, ref mut j, _)| std::mem::swap(i, j)); - let indices = self.1.unapply_indices_unchecked(&indices); - indices + .for_each(|UnapplyBinaryProduct(_, ref mut j, ref mut k)| std::mem::swap(j, k)); + let idx = self.1.unapply_indices_unchecked(&idx); + let idx = idx .into_iter() - .map(|UnapplyBinaryProduct(i, j, k)| k.set(j * self.0.len_out() + i)) - .collect() + .map(|UnapplyBinaryProduct(i, j, k)| indices[i].set(j * self.1.len_out() + k)) + .collect(); + idx } #[inline] fn is_identity(&self) -> bool { @@ -438,14 +441,14 @@ impl Map for BinaryProduct { } #[derive(Debug, Clone)] -struct UnapplyBinaryProduct(usize, usize, T); +struct UnapplyBinaryProduct(usize, usize, usize); -impl UnapplyIndicesData for UnapplyBinaryProduct { +impl UnapplyIndicesData for UnapplyBinaryProduct { fn get(&self) -> usize { - self.0 + self.2 } fn set(&self, index: usize) -> Self { - Self(index, self.1, self.2.clone()) + Self(self.0, self.1, index) } } @@ -466,15 +469,6 @@ where pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, M> { self.0.iter() } - fn unravel_index(&self, index: usize) -> Vec { - let mut indices: Vec<_> = self.iter().rev().scan(index, |index, map| { - let (i0, i1) = index.div_rem(&map.len_in()); - *index = i0; - Some(i1) - }).collect(); - indices.reverse(); - indices - } } //impl Map for UniformProduct @@ -566,10 +560,11 @@ macro_rules! dispatch { $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { if $self.0.deref().len() == 1 { $self.0.deref()[0].$fn($($arg),*) - //} else if $self.0.deref().len() == 2 { - // BinaryProduct(&$self.0.deref()[0], $self.0.deref()[1]).$fn($($arg),*) } else { - BinaryProduct(UniformProduct(&$self.0.deref()[..1]), UniformProduct(&$self.0.deref()[1..])).$fn($($arg),*) + BinaryProduct( + UniformProduct(&$self.0.deref()[..1]), + UniformProduct(&$self.0.deref()[1..]), + ).$fn($($arg),*) } } }; @@ -578,10 +573,11 @@ macro_rules! dispatch { $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { if $self.0.deref().len() == 1 { $self.0.deref_mut()[0].$fn($($arg),*) - //} else if $self.0.deref().len() == 2 { - // BinaryProduct(&$self.0.deref_mut()[0], &$self.0.deref_mut()[1]).$fn($($arg),*) } else { - BinaryProduct(UniformProduct(&$self.0.deref_mut()[..1]), UniformProduct(&$self.0.deref_mut()[1..])).$fn($($arg),*) + BinaryProduct( + UniformProduct(&$self.0.deref_mut()[..1]), + UniformProduct(&$self.0.deref_mut()[1..]), + ).$fn($($arg),*) } } }; @@ -749,16 +745,31 @@ mod tests { #[test] fn uniform_product1() { let map = UniformProduct::new(vec![ - elementaries![Line*1 <- Edges], - elementaries![Line*1 <- Children], + elementaries![Line*2 <- Edges], + elementaries![Line*3], ]); - assert_eq!(map.len_out(), 1); - assert_eq!(map.len_in(), 4); + assert_eq!(map.len_out(), 6); + assert_eq!(map.len_in(), 12); assert_eq!(map.dim_in(), 1); assert_eq!(map.dim_out(), 2); - assert_map_apply!(map, 0, [[0.0], [1.0]], 0, [[1.0, 0.0], [1.0, 0.5]]); - assert_map_apply!(map, 1, [[0.0], [1.0]], 0, [[1.0, 0.5], [1.0, 1.0]]); - assert_map_apply!(map, 2, [[0.0], [1.0]], 0, [[0.0, 0.0], [0.0, 0.5]]); - assert_map_apply!(map, 3, [[0.0], [1.0]], 0, [[0.0, 0.5], [0.0, 1.0]]); + assert_map_apply!(map, 0, [[0.2], [0.3]], 0, [[1.0, 0.2], [1.0, 0.3]]); + assert_map_apply!(map, 1, [[0.2], [0.3]], 1, [[1.0, 0.2], [1.0, 0.3]]); + assert_map_apply!(map, 2, [[0.2], [0.3]], 2, [[1.0, 0.2], [1.0, 0.3]]); + assert_map_apply!(map, 3, [[0.2], [0.3]], 0, [[0.0, 0.2], [0.0, 0.3]]); + assert_map_apply!(map, 4, [[0.2], [0.3]], 1, [[0.0, 0.2], [0.0, 0.3]]); + assert_map_apply!(map, 5, [[0.2], [0.3]], 2, [[0.0, 0.2], [0.0, 0.3]]); + assert_map_apply!(map, 6, [[0.2], [0.3]], 3, [[1.0, 0.2], [1.0, 0.3]]); + assert_map_apply!(map, 7, [[0.2], [0.3]], 4, [[1.0, 0.2], [1.0, 0.3]]); + assert_map_apply!(map, 8, [[0.2], [0.3]], 5, [[1.0, 0.2], [1.0, 0.3]]); + assert_map_apply!(map, 9, [[0.2], [0.3]], 3, [[0.0, 0.2], [0.0, 0.3]]); + assert_map_apply!(map, 10, [[0.2], [0.3]], 4, [[0.0, 0.2], [0.0, 0.3]]); + assert_map_apply!(map, 11, [[0.2], [0.3]], 5, [[0.0, 0.2], [0.0, 0.3]]); + assert_eq!(map.apply_index(12), None); + assert_eq!(map.unapply_indices(&[0]), Some(vec![0, 3])); + assert_eq!(map.unapply_indices(&[1]), Some(vec![1, 4])); + assert_eq!(map.unapply_indices(&[2]), Some(vec![2, 5])); + assert_eq!(map.unapply_indices(&[3]), Some(vec![6, 9])); + assert_eq!(map.unapply_indices(&[4]), Some(vec![7, 10])); + assert_eq!(map.unapply_indices(&[5]), Some(vec![8, 11])); } } From c33e4adf3543b7e9023cef72bf22c1916e3c233e Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Thu, 30 Jun 2022 21:52:34 +0200 Subject: [PATCH 27/45] WIP --- src/elementary.rs | 305 ++++++++++------ src/lib.rs | 3 +- src/ops.rs | 49 ++- src/relative.rs | 866 ++++++++++++++++++++++++---------------------- src/util.rs | 41 +++ 5 files changed, 742 insertions(+), 522 deletions(-) create mode 100644 src/util.rs diff --git a/src/elementary.rs b/src/elementary.rs index 7dc1e4d1a..cebc0b33d 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -107,7 +107,7 @@ impl AddOffset for Identity { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Offset(M, usize); +pub struct Offset(pub M, pub usize); impl UnboundedMap for Offset { #[inline] @@ -562,98 +562,6 @@ impl Elementary { _ => None, } } - pub fn shift_inner_to_outer( - &self, - items: &[Self], - ) -> Option<(Option, Self, Vec)> { - if self.is_transpose() { - return None; - } - let mut target = self.clone(); - let mut shifted_items: Vec = Vec::new(); - let mut queue: Vec = Vec::new(); - let mut stride_out = 1; - let mut stride_in = 1; - for mut item in items.iter().cloned() { - // Swap matching edges and children at the same offset. - if let Self::Edges(Offset(Edges(esimplex), eoffset)) = &item { - if let Self::Children(Offset(Children(ref mut csimplex), coffset)) = &mut target { - if eoffset == coffset && esimplex.edge_dim() == csimplex.dim() { - if stride_in != 1 && self.mod_in() != 1 { - shifted_items.push(Self::new_transpose(stride_in, self.mod_in())); - } - shifted_items.append(&mut queue); - if stride_out != 1 && self.mod_in() != 1 { - shifted_items.push(Self::new_transpose(self.mod_in(), stride_out)); - } - shifted_items.push(Self::new_take( - esimplex.swap_edges_children_map(), - esimplex.nedges() * esimplex.nchildren(), - )); - shifted_items.push(Self::Edges(Offset(Edges(*esimplex), *eoffset))); - *csimplex = *esimplex; - stride_in = 1; - stride_out = 1; - continue; - } - } - } - // Update strides. - if self.mod_in() == 1 && self.mod_out() == 1 { - } else if self.mod_out() == 1 { - let n = stride_out.gcd(&item.mod_in()); - stride_out = stride_out / n * item.mod_out(); - stride_in *= item.mod_in() / n; - } else if let Some(Transpose(ref mut m, ref mut n)) = item.as_transpose_mut() { - if stride_out % (*m * *n) == 0 { - } else if stride_out % *n == 0 && (*m * *n) % (stride_out * self.mod_out()) == 0 { - stride_out /= *n; - *m = *m / self.mod_out() * self.mod_in(); - } else if *n % stride_out == 0 && *n % (stride_out * self.mod_out()) == 0 { - stride_out *= *m; - *n = *n / self.mod_out() * self.mod_in(); - } else { - return None; - } - } else if stride_out % item.mod_in() == 0 { - stride_out = stride_out / item.mod_in() * item.mod_out(); - } else { - return None; - } - // Update offsets. - let item_delta_dim = item.delta_dim(); - let target_delta_dim = target.delta_dim(); - let item_dim_in = item.dim_in(); - let target_dim_out = target.dim_out(); - if let (Some(item_offset), Some(target_offset)) = - (item.offset_mut(), target.offset_mut()) - { - if item_dim_in <= *target_offset { - *target_offset += item_delta_dim; - } else if target_dim_out <= *item_offset { - *item_offset -= target_delta_dim; - } else { - return None; - } - } - if !item.is_identity() { - queue.push(item); - } - } - if stride_in != 1 && self.mod_in() != 1 { - shifted_items.push(Self::new_transpose(stride_in, self.mod_in())); - } - shifted_items.extend(queue); - if stride_out != 1 && self.mod_in() != 1 { - shifted_items.push(Self::new_transpose(self.mod_in(), stride_out)); - } - let outer_transpose = if self.mod_out() == 1 || stride_out == 1 { - None - } else { - Some(Transpose::new(stride_out, self.mod_out())) - }; - Some((outer_transpose, target, shifted_items)) - } #[inline] const fn is_transpose(&self) -> bool { matches!(self, Self::Transpose(_)) @@ -819,7 +727,9 @@ where } #[inline] fn unapply_indices(&self, indices: &[T]) -> Vec { - self.iter().rev().fold(indices.to_vec(), |indices, map| map.unapply_indices(&indices)) + self.iter().rev().fold(indices.to_vec(), |indices, map| { + map.unapply_indices(&indices) + }) } #[inline] fn is_identity(&self) -> bool { @@ -878,6 +788,9 @@ impl WithBounds { len_out, } } + pub fn unbounded(&self) -> &M { + &self.map + } } impl Map for WithBounds { @@ -925,6 +838,203 @@ impl Map for WithBounds { } } +pub trait ShiftInnerToOuter { + type Output; + + fn shift_inner_to_outer( + &self, + inner: &Elementary, + stride: usize, + ) -> Option<((Elementary, usize), Self::Output)>; +} + +impl ShiftInnerToOuter for [Elementary] { + type Output = Vec; + + fn shift_inner_to_outer( + &self, + inner: &Elementary, + stride: usize, + ) -> Option<((Elementary, usize), Self::Output)> { + if inner.is_transpose() { + return None; + } + let mut target = inner.clone(); + let mut shifted_items: Vec = Vec::new(); + let mut queue: Vec = Vec::new(); + let mut stride_out = stride; + let mut stride_in = stride; + for mut item in self.iter().cloned() { + // Swap matching edges and children at the same offset. + if let Elementary::Edges(Offset(Edges(esimplex), eoffset)) = &item { + if let Elementary::Children(Offset(Children(ref mut csimplex), coffset)) = + &mut target + { + if eoffset == coffset && esimplex.edge_dim() == csimplex.dim() { + if stride_in != 1 && inner.mod_in() != 1 { + shifted_items + .push(Elementary::new_transpose(stride_in, inner.mod_in())); + } + shifted_items.append(&mut queue); + if stride_out != 1 && inner.mod_in() != 1 { + shifted_items + .push(Elementary::new_transpose(inner.mod_in(), stride_out)); + } + shifted_items.push(Elementary::new_take( + esimplex.swap_edges_children_map(), + esimplex.nedges() * esimplex.nchildren(), + )); + shifted_items.push(Elementary::Edges(Offset(Edges(*esimplex), *eoffset))); + *csimplex = *esimplex; + stride_in = 1; + stride_out = 1; + continue; + } + } + } + // Update strides. + if inner.mod_in() == 1 && inner.mod_out() == 1 { + } else if inner.mod_out() == 1 { + let n = stride_out.gcd(&item.mod_in()); + stride_out = stride_out / n * item.mod_out(); + stride_in *= item.mod_in() / n; + } else if let Some(Transpose(ref mut m, ref mut n)) = item.as_transpose_mut() { + if stride_out % (*m * *n) == 0 { + } else if stride_out % *n == 0 && (*m * *n) % (stride_out * inner.mod_out()) == 0 { + stride_out /= *n; + *m = *m / inner.mod_out() * inner.mod_in(); + } else if *n % stride_out == 0 && *n % (stride_out * inner.mod_out()) == 0 { + stride_out *= *m; + *n = *n / inner.mod_out() * inner.mod_in(); + } else { + return None; + } + } else if stride_out % item.mod_in() == 0 { + stride_out = stride_out / item.mod_in() * item.mod_out(); + } else { + return None; + } + // Update offsets. + let item_delta_dim = item.delta_dim(); + let target_delta_dim = target.delta_dim(); + let item_dim_in = item.dim_in(); + let target_dim_out = target.dim_out(); + if let (Some(item_offset), Some(target_offset)) = + (item.offset_mut(), target.offset_mut()) + { + if item_dim_in <= *target_offset { + *target_offset += item_delta_dim; + } else if target_dim_out <= *item_offset { + *item_offset -= target_delta_dim; + } else { + return None; + } + } + if !item.is_identity() { + queue.push(item); + } + } + if stride_in != 1 && target.mod_in() != 1 { + shifted_items.push(Elementary::new_transpose(stride_in, target.mod_in())); + } + shifted_items.extend(queue); + if stride_out != 1 && target.mod_in() != 1 { + shifted_items.push(Elementary::new_transpose(target.mod_in(), stride_out)); + } + if target.mod_out() == 1 { + stride_out = 1; + } + Some(((target, stride_out), shifted_items)) + } +} + +impl ShiftInnerToOuter for Vec { + type Output = Self; + + #[inline] + fn shift_inner_to_outer( + &self, + inner: &Elementary, + stride: usize, + ) -> Option<((Elementary, usize), Self::Output)> { + (&self[..]).shift_inner_to_outer(inner, stride) + } +} + +impl ShiftInnerToOuter for WithBounds +where + M: UnboundedMap + ShiftInnerToOuter, + M::Output: UnboundedMap, +{ + type Output = WithBounds; + + fn shift_inner_to_outer( + &self, + inner: &Elementary, + stride: usize, + ) -> Option<((Elementary, usize), Self::Output)> { + self.unbounded() + .shift_inner_to_outer(inner, stride) + .map(|(outer, slf)| { + let dim_in = self.dim_in() - inner.delta_dim(); + let len_in = self.len_in() / inner.mod_out() * inner.mod_in(); + (outer, WithBounds::new_unchecked(slf, dim_in, len_in)) + }) + } +} + +pub trait SplitOuterElementary: Sized { + type Output: IntoIterator; + + fn split_outer_elementary(&self) -> Self::Output; +} + +impl SplitOuterElementary for Vec { + type Output = Vec<((Elementary, usize), Self)>; + + fn split_outer_elementary(&self) -> Self::Output { + let mut splits = Vec::new(); + for (i, item) in self.iter().enumerate().rev() { + if let Some((outer, mut inner)) = (&self[..i]).shift_inner_to_outer(item, 1) { + inner.extend(self[i + 1..].iter().cloned()); + splits.push((outer, inner)); + } + if let Elementary::Edges(Offset(Edges(Simplex::Line), offset)) = item { + let mut children = Elementary::new_children(Simplex::Line); + children.add_offset(*offset); + if let Some((outer, mut inner)) = (&self[..i]).shift_inner_to_outer(&children, 1) { + inner.push(item.clone()); + inner.push(Elementary::new_take( + Simplex::Line.swap_edges_children_map(), + Simplex::Line.nedges() * Simplex::Line.nchildren(), + )); + inner.extend(self[i + 1..].iter().cloned()); + splits.push((outer, inner)); + } + } + } + splits + } +} + +impl SplitOuterElementary for WithBounds { + // TODO: As soons as we can use `impl Iterator`, drop the collect. + type Output = Vec<((Elementary, usize), Self)>; + + fn split_outer_elementary(&self) -> Self::Output { + self.unbounded() + .split_outer_elementary() + .into_iter() + .map(|(outer, unbounded)| { + ( + outer, + WithBounds::new_unchecked(unbounded, self.dim_in(), self.len_in()), + ) + }) + .collect() + } +} + #[cfg(test)] mod tests { use super::*; @@ -1119,12 +1229,13 @@ mod tests { macro_rules! assert_shift_inner_to_outer { ($($item:expr),*; $($simplex:ident),*) => {{ let unshifted = [$(Elementary::from($item),)*]; - let (ltrans, litem, lchain) = unshifted.first().unwrap().shift_inner_to_outer(&unshifted[1..]).unwrap(); + let ((litem, lstride), lchain) = (&unshifted[1..]).shift_inner_to_outer(&unshifted[0], 1).unwrap(); let mut shifted: Vec = Vec::new(); shifted.extend(lchain.into_iter()); + let litem_mod_out = litem.mod_out(); shifted.push(litem); - if let Some(ltrans) = ltrans { - shifted.push(ltrans.into()); + if lstride != 1 { + shifted.push(Elementary::new_transpose(lstride, litem_mod_out)); } assert_equiv_maps!(&shifted[..], &unshifted[..] $(, $simplex)*); }}; diff --git a/src/lib.rs b/src/lib.rs index 7bbbb2ff1..fc264181a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ pub mod elementary; pub mod finite_f64; pub mod ops; -//pub mod relative; +pub mod relative; pub mod simplex; +mod util; //pub mod tesselation; //pub mod topology; diff --git a/src/ops.rs b/src/ops.rs index 3742df5f4..31fb334ee 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,6 +1,6 @@ use crate::{Error, Map, UnapplyIndicesData}; -use std::ops::Deref; use num::Integer as _; +use std::ops::Deref; #[derive(Debug, Clone, PartialEq)] pub struct BinaryComposition(Inner, Outer); @@ -15,6 +15,12 @@ impl BinaryComposition { Ok(Self(inner, outer)) } } + pub fn inner(&self) -> &Inner { + &self.0 + } + pub fn outer(&self) -> &Outer { + &self.1 + } } impl Map for BinaryComposition { @@ -365,6 +371,12 @@ impl BinaryProduct { pub fn new(map0: M0, map1: M1) -> Self { Self(map0, map1) } + pub fn first(&self) -> &M0 { + &self.0 + } + pub fn second(&self) -> &M1 { + &self.1 + } } impl Map for BinaryProduct { @@ -424,8 +436,7 @@ impl Map for BinaryProduct { }) .collect(); let mut idx = self.0.unapply_indices_unchecked(&idx); - idx - .iter_mut() + idx.iter_mut() .for_each(|UnapplyBinaryProduct(_, ref mut j, ref mut k)| std::mem::swap(j, k)); let idx = self.1.unapply_indices_unchecked(&idx); let idx = idx @@ -469,6 +480,25 @@ where pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, M> { self.0.iter() } + pub fn strides_out(&self) -> Vec { + let mut strides = Vec::with_capacity(self.0.len()); + let mut stride = 1; + for map in self.iter().rev() { + strides.push(stride); + stride *= map.len_out(); + } + strides.reverse(); + strides + } + pub fn offsets_out(&self) -> Vec { + let mut offsets = Vec::with_capacity(self.0.len()); + let mut offset = 0; + for map in self.iter() { + offsets.push(offset); + offset += map.dim_out(); + } + offsets + } } //impl Map for UniformProduct @@ -528,7 +558,7 @@ where // UnapplyBinaryProduct(i, j, k.clone()) // }) // .collect(); -// +// // // let mut iter = self.iter(); // let map = iter.next().unwrap(); @@ -616,6 +646,15 @@ where dispatch! {fn is_identity(&self) -> bool} } +impl FromIterator for UniformProduct> { + fn from_iter(iter: T) -> Self + where + T: IntoIterator, + { + UniformProduct::new(iter.into_iter().collect()) + } +} + // pub struct OptionReorder(map, Option); #[cfg(test)] @@ -746,7 +785,7 @@ mod tests { fn uniform_product1() { let map = UniformProduct::new(vec![ elementaries![Line*2 <- Edges], - elementaries![Line*3], + elementaries![Line * 3], ]); assert_eq!(map.len_out(), 6); assert_eq!(map.len_in(), 12); diff --git a/src/relative.rs b/src/relative.rs index 7fdb3ac25..6690161c3 100644 --- a/src/relative.rs +++ b/src/relative.rs @@ -1,444 +1,472 @@ -use crate::elementary::{Edges, Elementary, Identity, Transpose}; -use crate::ops::{Concatenation, WithBounds}; +use crate::elementary::{ + Edges, Elementary, Identity, Offset, ShiftInnerToOuter, SplitOuterElementary, Transpose, + UnboundedMap, WithBounds, +}; +use crate::ops::{BinaryComposition, UniformProduct, BinaryProduct}; use crate::simplex::Simplex; -use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; +use crate::util::ReplaceNthIter as _; +use crate::{AddOffset, Map, UnapplyIndicesData}; use std::collections::BTreeMap; use std::iter; +use std::ops::Deref; use std::rc::Rc; -trait RemoveCommonPrefix: Sized { - fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self); - fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self); -} - -fn split_heads(items: &[Elementary]) -> BTreeMap<(Option, Elementary), Vec> { - let mut heads = BTreeMap::new(); - for (i, item) in items.iter().enumerate() { - if let Some((transpose, head, mut tail)) = item.shift_left(&items[..i]) { - tail.extend(items[i + 1..].iter().cloned()); - heads.insert((transpose, head), tail); - } - if let Elementary::Edges(Edges(Simplex::Line, offset)) = item { - let children = Elementary::new_children(Simplex::Line).with_offset(*offset); - if let Some((transpose, head, mut tail)) = children.shift_left(&items[..i]) { - tail.push(item.clone()); - tail.push(Elementary::new_take( - Simplex::Line.swap_edges_children_map(), - Simplex::Line.nedges() * Simplex::Line.nchildren(), - )); - tail.extend(items[i + 1..].iter().cloned()); - heads.insert((transpose, head), tail); - } - } - } - heads -} - -impl RemoveCommonPrefix for Vec { - /// Remove and return the common prefix of two maps, transforming either if necessary. - fn remove_common_prefix(&self, other: &Self) -> (Self, Self, Self) { - let mut common = Vec::new(); - let mut tail1 = self.clone(); - let mut tail2 = other.clone(); - while !tail1.is_empty() && !tail2.is_empty() { - if tail1.first() == tail2.first() { - let mut iter1 = tail1.drain(..); - let mut iter2 = tail2.drain(..); - common.push(iter1.next().unwrap()); - iter2.next(); - tail1 = iter1.collect(); - tail2 = iter2.collect(); - continue; - } - let heads1 = split_heads(&tail1); - let mut heads2 = split_heads(&tail2); - if let Some(((transpose, head), new_tail1, new_tail2)) = heads1 - .into_iter() - .filter_map(|(key, t1)| heads2.remove(&key).map(|t2| (key, t1, t2))) - .min_by_key(|(_, t1, t2)| std::cmp::max(t1.len(), t2.len())) - { - if let Some(transpose) = transpose { - common.push(transpose.into()); - } - common.push(head); - tail1 = new_tail1; - tail2 = new_tail2; - continue; - } - break; - } - let common = if tail1.is_empty() && (!tail2.is_empty() || self.len() <= other.len()) { - self.clone() - } else if tail2.is_empty() { - other.clone() - } else { - common - }; - (common, tail1, tail2) - } - fn remove_common_prefix_opt_lhs(&self, other: &Self) -> (Self, Self, Self) { - let (common, mut tail1, mut tail2) = self.remove_common_prefix(other); - // Move transposes at the front of `tail1` to `tail2`. - tail1.reverse(); - tail2.reverse(); - while let Some(mut item) = tail1.pop() { - if let Some(transpose) = item.as_transpose_mut() { - transpose.reverse(); - tail2.push(item); - } else { - tail1.push(item); - break; - } - } - tail1.reverse(); - tail2.reverse(); - (common, tail1, tail2) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Relative { - Identity(WithBounds), - Single(WithBounds>), - Multiple(RelativeMultiple), - Concatenation(Concatenation), -} - -macro_rules! dispatch { - ( - $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( - &$self:ident $(, $arg:ident: $ty:ty)* - ) $(-> $ret:ty)? - ) => { - #[inline] - $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { - dispatch!(@match $self; $fn; $($arg),*) - } - }; - ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { - #[inline] - $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { - dispatch!(@match $self; $fn; $($arg),*) - } - }; - (@match $self:ident; $fn:ident; $($arg:ident),*) => { - match $self { - Relative::Identity(var) => var.$fn($($arg),*), - Relative::Single(var) => var.$fn($($arg),*), - Relative::Multiple(var) => var.$fn($($arg),*), - Relative::Concatenation(var) => var.$fn($($arg),*), - } - } -} - -impl BoundedMap for Relative { - dispatch! {fn len_out(&self) -> usize} - dispatch! {fn len_in(&self) -> usize} - dispatch! {fn dim_out(&self) -> usize} - dispatch! {fn dim_in(&self) -> usize} - dispatch! {fn delta_dim(&self) -> usize} - dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} - dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} - dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} - dispatch! {fn apply_index(&self, index: usize) -> Option} - dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} - dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} - dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} - dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} - dispatch! {fn is_identity(&self) -> bool} -} - -impl AddOffset for Relative { - dispatch! {fn add_offset(&mut self, offset: usize)} -} - -pub trait RelativeTo { - fn relative_to(&self, target: &Target) -> Option; - fn unapply_indices_from( - &self, - target: &Target, - indices: &[T], - ) -> Option> { - self.relative_to(target) - .and_then(|rel| rel.unapply_indices(indices)) - } -} - -impl RelativeTo for WithBounds> { - fn relative_to(&self, target: &Self) -> Option { - let (_, rem, rel) = target - .get_unbounded() - .remove_common_prefix_opt_lhs(self.get_unbounded()); - rem.is_identity() - .then(|| Relative::Single(Self::new_unchecked(rel, target.dim_in(), target.len_in()))) - } -} - -impl RelativeTo for Concatenation +impl SplitOuterElementary for UniformProduct> where - Item: BoundedMap + RelativeTo, - Target: BoundedMap, + M: Map + SplitOuterElementary + Clone, { - fn relative_to(&self, target: &Target) -> Option { - self.iter() - .map(|item| item.relative_to(target)) - .collect::>() - .map(|rel_items| Relative::Concatenation(Concatenation::new(rel_items))) - } -} + type Output = Vec<((Elementary, usize), Self)>; -fn pop_common(vecs: &mut [&mut Vec]) -> Option { - let item = vecs.first().and_then(|vec| vec.last()); - if item.is_some() && vecs[1..].iter().all(|vec| vec.last() == item) { - for vec in vecs[1..].iter_mut() { - vec.pop(); - } - vecs[0].pop() - } else { - None + fn split_outer_elementary(&self) -> Self::Output { + self.iter() + .enumerate() + .zip(self.offsets_out()) + .zip(self.strides_out()) + .flat_map(|(((iprod, item), prod_offset), prod_stride)| { + item.split_outer_elementary().into_iter().map( + move |((mut elmtry, mut stride), inner)| { + elmtry.add_offset(prod_offset); + stride *= prod_stride; + let product = self.iter().cloned().replace_nth(iprod, inner).collect(); + ((elmtry, stride), product) + }, + ) + }) + .collect() } } -#[derive(Debug, Clone)] -struct IndexOutIn(usize, usize); +impl SplitOuterElementary for BinaryProduct +where + M0: Map + SplitOuterElementary + Clone, + M1: Map + SplitOuterElementary + Clone, +{ + type Output = Vec<((Elementary, usize), Self)>; -impl UnapplyIndicesData for IndexOutIn { - #[inline] - fn last(&self) -> usize { - self.1 - } - #[inline] - fn push(&self, index: usize) -> Self { - Self(self.0, index) + fn split_outer_elementary(&self) -> Self::Output { + let first = self.first().split_outer_elementary().into_iter().map( + |((elmtry, mut stride), first)| { + stride *= self.second().len_out(); + let product = BinaryProduct::new(first, self.second().clone()); + ((elmtry, stride), product) + }); + let second = self.second().split_outer_elementary().into_iter().map( + |((mut elmtry, stride), second)| { + elmtry.add_offset(self.first().dim_out()); + let product = BinaryProduct::new(self.first().clone(), second); + ((elmtry, stride), product) + }); + first.chain(second).collect() } } -#[derive(Debug, Clone, PartialEq)] -pub struct RelativeMultiple { - rels: Vec>, - index_map: Rc>, - common: Vec, - len_out: usize, - len_in: usize, - dim_in: usize, - delta_dim: usize, -} +// TODO: BinaryProduct +// TODO: UniformComposition? -impl BoundedMap for RelativeMultiple { - fn dim_in(&self) -> usize { - self.dim_in - } - fn delta_dim(&self) -> usize { - self.delta_dim - } - fn len_in(&self) -> usize { - self.len_in - } - fn len_out(&self) -> usize { - self.len_out - } - fn apply_inplace_unchecked( - &self, - index: usize, - coordinates: &mut [f64], - stride: usize, - offset: usize, - ) -> usize { - let index = self - .common - .apply_inplace(index, coordinates, stride, offset); - let (iout, iin) = self.index_map[index]; - let n = self.index_map.len(); - self.rels[iin / n].apply_inplace(iin % n, coordinates, stride, offset); - iout - } - fn apply_index_unchecked(&self, index: usize) -> usize { - self.index_map[self.common.apply_index(index)].0 - } - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.common.apply_indices_inplace(indices); - for index in indices.iter_mut() { - *index = self.index_map[*index].0; - } - } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - // FIXME: VERY EXPENSIVE!!! - let mut in_indices: Vec = Vec::new(); - for index in indices { - in_indices.extend( - self.index_map - .iter() - .enumerate() - .filter_map(|(iin, (iout, _))| { - (*iout == index.last()).then(|| index.push(iin)) - }), - ); - } - self.common.unapply_indices(&in_indices) - } - fn is_identity(&self) -> bool { - false - } -} - -impl AddOffset for RelativeMultiple { - fn add_offset(&mut self, offset: usize) { - self.common.add_offset(offset); - for rel in self.rels.iter_mut() { - rel.add_offset(offset); - } - self.dim_in += offset; - } -} +impl SplitOuterElementary for BinaryComposition +where + Inner: Map + SplitOuterElementary + Clone, + Outer: Map + SplitOuterElementary + ShiftInnerToOuter, +{ + type Output = Vec<((Elementary, usize), Self)>; -impl RelativeTo> for WithBounds> { - fn relative_to(&self, targets: &Concatenation) -> Option { - let mut rels_indices = Vec::new(); - let mut offset = 0; - for target in targets.iter() { - let (_, rem, rel) = target - .get_unbounded() - .remove_common_prefix_opt_lhs(self.get_unbounded()); - if rem.is_identity() { - let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); - let rel: Vec = iter::once(slice).chain(rel).collect(); - let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); - return Some(Relative::Single(rel)); - } - if rem.dim_out() == 0 { - let mut indices: Vec = (0..target.len_in()).collect(); - rem.apply_indices_inplace(&mut indices); - rels_indices.push((rel, offset, indices)) - } - offset += target.len_in(); - } - // Split off common tail. TODO: Only shape increasing items, not take, slice (and transpose?). - let common_len_out = self.len_in(); - let common = Vec::new(); - //let mut common_len_out = self.len_in(); - //let mut common = Vec::new(); - //{ - // let mut rels: Vec<_> = rels_indices.iter_mut().map(|(rel, _, _)| rel).collect(); - // while let Some(item) = pop_common(&mut rels[..]) { - // common_len_out = common_len_out / item.mod_in() * item.mod_out(); - // common.push(item); - // } - //} - // Build index map. - let mut index_map: Vec> = - iter::repeat(None).take(common_len_out).collect(); - let mut rels = Vec::new(); - for (irel, (rel, offset, out_indices)) in rels_indices.into_iter().enumerate() { - let rel_indices: Vec<_> = (offset..offset + out_indices.len()) - .zip(out_indices) - .map(|(i, j)| IndexOutIn(i, j)) - .collect(); - for IndexOutIn(iout, iin) in rel.unapply_indices(&rel_indices) { - assert!( - index_map[iin].is_none(), - "target contains duplicate entries" - ); - index_map[iin] = Some((iout, iin + irel * common_len_out)); - } - rels.push(rel); - } - index_map + fn split_outer_elementary(&self) -> Self::Output { + let mut split: Vec<_> = self + .outer() + .split_outer_elementary() .into_iter() - .collect::>>() - .map(|index_map| { - Relative::Multiple(RelativeMultiple { - index_map: index_map.into(), - rels, - common, - delta_dim: targets.dim_in() - self.dim_in(), - dim_in: self.dim_in(), - len_out: targets.len_in(), - len_in: self.len_in(), - }) + .map(|(out, outer)| { + ( + out, + BinaryComposition::new(self.inner().clone(), outer).unwrap(), + ) }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::elementary::*; - use crate::ops::Composition; - use crate::simplex::Simplex::*; - use approx::assert_abs_diff_eq; - use std::iter; - - #[test] - fn remove_common_prefix() { - let c1 = Elementary::new_children(Line); - let e1 = Elementary::new_edges(Line); - let swap_ec1 = Elementary::new_take(vec![2, 1], 4); - let a = vec![c1.clone(), c1.clone()]; - let b = vec![e1.clone()]; - assert_eq!( - a.remove_common_prefix(&b), - ( - vec![c1.clone(), c1.clone()], - vec![], - vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], - ) + .collect(); + split.extend( + self.inner() + .split_outer_elementary() + .into_iter() + .filter_map(|((elmtry, stride), inner)| { + self.outer() + .shift_inner_to_outer(&elmtry, stride) + .map(|(out, outer)| (out, Self::new(inner, outer).unwrap())) + }), ); + split } +} - macro_rules! single { - (dim=$dim_out:literal, len=$len_out:literal) => { - WithBounds::>::new(Vec::new(), $dim_out, $len_out).unwrap() - }; - (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { - WithBounds::>::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() - }; - } - - macro_rules! assert_equiv_maps { - ($a:expr, $b:expr $(, $simplex:ident)*) => {{ - let a = $a; - let b = $b; - println!("a: {a:?}"); - println!("b: {b:?}"); - // Build coords: the outer product of the vertices of the given simplices, zero-padded - // to the dimension of the root. - let coords = iter::once([]); - let simplex_dim = 0; - $( - let coords = coords.flat_map(|coord| { - $simplex - .vertices() - .chunks($simplex.dim()) - .map(move |vert| [&coord, vert].concat()) - }); - let simplex_dim = simplex_dim + $simplex.dim(); - )* - assert_eq!(simplex_dim, a.dim_in(), "the given simplices don't add up to the input dimension"); - let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); - let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); - // Test if every input maps to the same output for both `a` and `b`. - for i in 0..2 * a.len_in() { - let mut crds_a = coords.clone(); - let mut crds_b = coords.clone(); - let ja = a.apply_inplace(i, &mut crds_a, a.dim_out(), 0); - let jb = b.apply_inplace(i, &mut crds_b, a.dim_out(), 0); - assert_eq!(ja, jb, "i={i}"); - assert_abs_diff_eq!(crds_a[..], crds_b[..]); +pub fn remove_common_outer(mut map1: M1, mut map2: M2) -> (Vec, M1, M2) +where + M1: Map + SplitOuterElementary, + M2: Map + SplitOuterElementary, +{ + let mut common = Vec::new(); + while !map1.is_identity() && !map2.is_identity() { + let mut outers2: BTreeMap<_, _> = map2.split_outer_elementary().into_iter().collect(); + if let Some(((outer, stride), new_map1, new_map2)) = map1 + .split_outer_elementary() + .into_iter() + .filter_map(|(key, t1)| outers2.remove(&key).map(|t2| (key, t1, t2))) + .next() + { + if stride != 1 { + common.push(Elementary::new_transpose(stride, outer.mod_out())); } - }}; - } - - #[test] - fn rel_to() { - let a1 = single!(dim=1, len=2 <- Children::new(Line), Take::new(vec![0, 2], 4)); - let a2 = single!(dim=1, len=2 <- Children::new(Line), Take::new(vec![1, 3], 4), Children::new(Line)); - let a = Concatenation::new(vec![a1, a2]); - let b = - single!(dim=1, len=2 <- Children::new(Line), Children::new(Line), Children::new(Line)); - assert_equiv_maps!( - Composition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), - b, - Line - ); + common.push(outer); + map1 = new_map1; + map2 = new_map2; + continue; + } + break; } + common.reverse(); + (common, map1, map2) } + +//#[derive(Debug, Clone, PartialEq)] +//pub enum Relative { +// Identity(WithBounds), +// Single(WithBounds>), +// Multiple(RelativeMultiple), +// Concatenation(Concatenation), +//} +// +//macro_rules! dispatch { +// ( +// $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( +// &$self:ident $(, $arg:ident: $ty:ty)* +// ) $(-> $ret:ty)? +// ) => { +// #[inline] +// $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { +// dispatch!(@match $self; $fn; $($arg),*) +// } +// }; +// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { +// #[inline] +// $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { +// dispatch!(@match $self; $fn; $($arg),*) +// } +// }; +// (@match $self:ident; $fn:ident; $($arg:ident),*) => { +// match $self { +// Relative::Identity(var) => var.$fn($($arg),*), +// Relative::Single(var) => var.$fn($($arg),*), +// Relative::Multiple(var) => var.$fn($($arg),*), +// Relative::Concatenation(var) => var.$fn($($arg),*), +// } +// } +//} +// +//impl BoundedMap for Relative { +// dispatch! {fn len_out(&self) -> usize} +// dispatch! {fn len_in(&self) -> usize} +// dispatch! {fn dim_out(&self) -> usize} +// dispatch! {fn dim_in(&self) -> usize} +// dispatch! {fn delta_dim(&self) -> usize} +// dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} +// dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} +// dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} +// dispatch! {fn apply_index(&self, index: usize) -> Option} +// dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} +// dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} +// dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} +// dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} +// dispatch! {fn is_identity(&self) -> bool} +//} +// +//impl AddOffset for Relative { +// dispatch! {fn add_offset(&mut self, offset: usize)} +//} +// +//pub trait RelativeTo { +// fn relative_to(&self, target: &Target) -> Option; +// fn unapply_indices_from( +// &self, +// target: &Target, +// indices: &[T], +// ) -> Option> { +// self.relative_to(target) +// .and_then(|rel| rel.unapply_indices(indices)) +// } +//} +// +//impl RelativeTo for WithBounds> { +// fn relative_to(&self, target: &Self) -> Option { +// let (_, rem, rel) = target +// .get_unbounded() +// .remove_common_prefix_opt_lhs(self.get_unbounded()); +// rem.is_identity() +// .then(|| Relative::Single(Self::new_unchecked(rel, target.dim_in(), target.len_in()))) +// } +//} +// +//impl RelativeTo for Concatenation +//where +// Item: BoundedMap + RelativeTo, +// Target: BoundedMap, +//{ +// fn relative_to(&self, target: &Target) -> Option { +// self.iter() +// .map(|item| item.relative_to(target)) +// .collect::>() +// .map(|rel_items| Relative::Concatenation(Concatenation::new(rel_items))) +// } +//} +// +//fn pop_common(vecs: &mut [&mut Vec]) -> Option { +// let item = vecs.first().and_then(|vec| vec.last()); +// if item.is_some() && vecs[1..].iter().all(|vec| vec.last() == item) { +// for vec in vecs[1..].iter_mut() { +// vec.pop(); +// } +// vecs[0].pop() +// } else { +// None +// } +//} +// +//#[derive(Debug, Clone)] +//struct IndexOutIn(usize, usize); +// +//impl UnapplyIndicesData for IndexOutIn { +// #[inline] +// fn last(&self) -> usize { +// self.1 +// } +// #[inline] +// fn push(&self, index: usize) -> Self { +// Self(self.0, index) +// } +//} +// +//#[derive(Debug, Clone, PartialEq)] +//pub struct RelativeMultiple { +// rels: Vec>, +// index_map: Rc>, +// common: Vec, +// len_out: usize, +// len_in: usize, +// dim_in: usize, +// delta_dim: usize, +//} +// +//impl BoundedMap for RelativeMultiple { +// fn dim_in(&self) -> usize { +// self.dim_in +// } +// fn delta_dim(&self) -> usize { +// self.delta_dim +// } +// fn len_in(&self) -> usize { +// self.len_in +// } +// fn len_out(&self) -> usize { +// self.len_out +// } +// fn apply_inplace_unchecked( +// &self, +// index: usize, +// coordinates: &mut [f64], +// stride: usize, +// offset: usize, +// ) -> usize { +// let index = self +// .common +// .apply_inplace(index, coordinates, stride, offset); +// let (iout, iin) = self.index_map[index]; +// let n = self.index_map.len(); +// self.rels[iin / n].apply_inplace(iin % n, coordinates, stride, offset); +// iout +// } +// fn apply_index_unchecked(&self, index: usize) -> usize { +// self.index_map[self.common.apply_index(index)].0 +// } +// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { +// self.common.apply_indices_inplace(indices); +// for index in indices.iter_mut() { +// *index = self.index_map[*index].0; +// } +// } +// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { +// // FIXME: VERY EXPENSIVE!!! +// let mut in_indices: Vec = Vec::new(); +// for index in indices { +// in_indices.extend( +// self.index_map +// .iter() +// .enumerate() +// .filter_map(|(iin, (iout, _))| { +// (*iout == index.last()).then(|| index.push(iin)) +// }), +// ); +// } +// self.common.unapply_indices(&in_indices) +// } +// fn is_identity(&self) -> bool { +// false +// } +//} +// +//impl AddOffset for RelativeMultiple { +// fn add_offset(&mut self, offset: usize) { +// self.common.add_offset(offset); +// for rel in self.rels.iter_mut() { +// rel.add_offset(offset); +// } +// self.dim_in += offset; +// } +//} +// +//impl RelativeTo> for WithBounds> { +// fn relative_to(&self, targets: &Concatenation) -> Option { +// let mut rels_indices = Vec::new(); +// let mut offset = 0; +// for target in targets.iter() { +// let (_, rem, rel) = target +// .get_unbounded() +// .remove_common_prefix_opt_lhs(self.get_unbounded()); +// if rem.is_identity() { +// let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); +// let rel: Vec = iter::once(slice).chain(rel).collect(); +// let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); +// return Some(Relative::Single(rel)); +// } +// if rem.dim_out() == 0 { +// let mut indices: Vec = (0..target.len_in()).collect(); +// rem.apply_indices_inplace(&mut indices); +// rels_indices.push((rel, offset, indices)) +// } +// offset += target.len_in(); +// } +// // Split off common tail. TODO: Only shape increasing items, not take, slice (and transpose?). +// let common_len_out = self.len_in(); +// let common = Vec::new(); +// //let mut common_len_out = self.len_in(); +// //let mut common = Vec::new(); +// //{ +// // let mut rels: Vec<_> = rels_indices.iter_mut().map(|(rel, _, _)| rel).collect(); +// // while let Some(item) = pop_common(&mut rels[..]) { +// // common_len_out = common_len_out / item.mod_in() * item.mod_out(); +// // common.push(item); +// // } +// //} +// // Build index map. +// let mut index_map: Vec> = +// iter::repeat(None).take(common_len_out).collect(); +// let mut rels = Vec::new(); +// for (irel, (rel, offset, out_indices)) in rels_indices.into_iter().enumerate() { +// let rel_indices: Vec<_> = (offset..offset + out_indices.len()) +// .zip(out_indices) +// .map(|(i, j)| IndexOutIn(i, j)) +// .collect(); +// for IndexOutIn(iout, iin) in rel.unapply_indices(&rel_indices) { +// assert!( +// index_map[iin].is_none(), +// "target contains duplicate entries" +// ); +// index_map[iin] = Some((iout, iin + irel * common_len_out)); +// } +// rels.push(rel); +// } +// index_map +// .into_iter() +// .collect::>>() +// .map(|index_map| { +// Relative::Multiple(RelativeMultiple { +// index_map: index_map.into(), +// rels, +// common, +// delta_dim: targets.dim_in() - self.dim_in(), +// dim_in: self.dim_in(), +// len_out: targets.len_in(), +// len_in: self.len_in(), +// }) +// }) +// } +//} +// +//#[cfg(test)] +//mod tests { +// use super::*; +// use crate::elementary::*; +// use crate::ops::Composition; +// use crate::simplex::Simplex::*; +// use approx::assert_abs_diff_eq; +// use std::iter; +// +// #[test] +// fn remove_common_prefix() { +// let c1 = Elementary::new_children(Line); +// let e1 = Elementary::new_edges(Line); +// let swap_ec1 = Elementary::new_take(vec![2, 1], 4); +// let a = vec![c1.clone(), c1.clone()]; +// let b = vec![e1.clone()]; +// assert_eq!( +// a.remove_common_prefix(&b), +// ( +// vec![c1.clone(), c1.clone()], +// vec![], +// vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], +// ) +// ); +// } +// +// macro_rules! single { +// (dim=$dim_out:literal, len=$len_out:literal) => { +// WithBounds::>::new(Vec::new(), $dim_out, $len_out).unwrap() +// }; +// (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { +// WithBounds::>::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() +// }; +// } +// +// macro_rules! assert_equiv_maps { +// ($a:expr, $b:expr $(, $simplex:ident)*) => {{ +// let a = $a; +// let b = $b; +// println!("a: {a:?}"); +// println!("b: {b:?}"); +// // Build coords: the outer product of the vertices of the given simplices, zero-padded +// // to the dimension of the root. +// let coords = iter::once([]); +// let simplex_dim = 0; +// $( +// let coords = coords.flat_map(|coord| { +// $simplex +// .vertices() +// .chunks($simplex.dim()) +// .map(move |vert| [&coord, vert].concat()) +// }); +// let simplex_dim = simplex_dim + $simplex.dim(); +// )* +// assert_eq!(simplex_dim, a.dim_in(), "the given simplices don't add up to the input dimension"); +// let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); +// let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); +// // Test if every input maps to the same output for both `a` and `b`. +// for i in 0..2 * a.len_in() { +// let mut crds_a = coords.clone(); +// let mut crds_b = coords.clone(); +// let ja = a.apply_inplace(i, &mut crds_a, a.dim_out(), 0); +// let jb = b.apply_inplace(i, &mut crds_b, a.dim_out(), 0); +// assert_eq!(ja, jb, "i={i}"); +// assert_abs_diff_eq!(crds_a[..], crds_b[..]); +// } +// }}; +// } +// +// #[test] +// fn rel_to() { +// let a1 = single!(dim=1, len=2 <- Children::new(Line), Take::new(vec![0, 2], 4)); +// let a2 = single!(dim=1, len=2 <- Children::new(Line), Take::new(vec![1, 3], 4), Children::new(Line)); +// let a = Concatenation::new(vec![a1, a2]); +// let b = +// single!(dim=1, len=2 <- Children::new(Line), Children::new(Line), Children::new(Line)); +// assert_equiv_maps!( +// Composition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), +// b, +// Line +// ); +// } +//} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 000000000..dd7098177 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,41 @@ +pub trait ReplaceNthIter: Iterator + Sized { + /// Replaces the nth item of the iterator with the given item. + fn replace_nth(self, index: usize, item: Self::Item) -> ReplaceNth; +} + +impl ReplaceNthIter for Iter { + fn replace_nth(self, index: usize, item: Iter::Item) -> ReplaceNth { + ReplaceNth(self, 0, index, item) + } +} + +pub struct ReplaceNth(Iter, usize, usize, Iter::Item); + +impl Iterator for ReplaceNth { + type Item = Iter::Item; + + fn next(&mut self) -> Option { + self.0.next().map(|mut value| { + if self.1 == self.2 { + std::mem::swap(&mut self.3, &mut value); + } + self.1 += 1; + value + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn replace_nth() { + let mut a = ['a', 'b', 'c', 'd'].into_iter().replace_nth(1, 'd'); + assert_eq!(a.next(), Some('a')); + assert_eq!(a.next(), Some('d')); // replaced + assert_eq!(a.next(), Some('c')); + assert_eq!(a.next(), Some('d')); + assert_eq!(a.next(), None); + } +} From 7cd0e7f60e3b14b1590c897f718f86a901f07c13 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Fri, 1 Jul 2022 13:42:13 +0200 Subject: [PATCH 28/45] WIP --- src/elementary.rs | 168 ++++++++++++++++------- src/ops.rs | 34 +---- src/relative.rs | 343 ++++++++++++++++++++++++---------------------- 3 files changed, 297 insertions(+), 248 deletions(-) diff --git a/src/elementary.rs b/src/elementary.rs index cebc0b33d..b84316ed6 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -838,20 +838,23 @@ impl Map for WithBounds { } } -pub trait ShiftInnerToOuter { +/// An interface for swapping a composition of an [`Elementary`] with `Self`. +pub trait SwapElementaryComposition { type Output; - fn shift_inner_to_outer( + /// Returns a map and an [`Elementary`] such that the composition is equivalent to + /// the composition of `inner` and `self`. + fn swap_elementary_composition( &self, inner: &Elementary, stride: usize, ) -> Option<((Elementary, usize), Self::Output)>; } -impl ShiftInnerToOuter for [Elementary] { +impl SwapElementaryComposition for [Elementary] { type Output = Vec; - fn shift_inner_to_outer( + fn swap_elementary_composition( &self, inner: &Elementary, stride: usize, @@ -948,33 +951,33 @@ impl ShiftInnerToOuter for [Elementary] { } } -impl ShiftInnerToOuter for Vec { +impl SwapElementaryComposition for Vec { type Output = Self; #[inline] - fn shift_inner_to_outer( + fn swap_elementary_composition( &self, inner: &Elementary, stride: usize, ) -> Option<((Elementary, usize), Self::Output)> { - (&self[..]).shift_inner_to_outer(inner, stride) + (&self[..]).swap_elementary_composition(inner, stride) } } -impl ShiftInnerToOuter for WithBounds +impl SwapElementaryComposition for WithBounds where - M: UnboundedMap + ShiftInnerToOuter, + M: UnboundedMap + SwapElementaryComposition, M::Output: UnboundedMap, { type Output = WithBounds; - fn shift_inner_to_outer( + fn swap_elementary_composition( &self, inner: &Elementary, stride: usize, ) -> Option<((Elementary, usize), Self::Output)> { self.unbounded() - .shift_inner_to_outer(inner, stride) + .swap_elementary_composition(inner, stride) .map(|(outer, slf)| { let dim_in = self.dim_in() - inner.delta_dim(); let len_in = self.len_in() / inner.mod_out() * inner.mod_in(); @@ -983,58 +986,123 @@ where } } -pub trait SplitOuterElementary: Sized { - type Output: IntoIterator; - - fn split_outer_elementary(&self) -> Self::Output; +/// An interface for iterating over all possible decompositions into `Self` and an [`Elementary`]. +pub trait AllElementaryDecompositions: Sized { + /// Return an iterator over all possible decompositions into `Self` and an [`Elementary`]. + /// + /// # Examples + /// + /// ``` + /// use nutils_test::simplex::Simplex::*; + /// use nutils_test::elementary::{Elementary, AllElementaryDecompositions as _}; + /// use nutils_test::elementaries; + /// let map = elementaries![Triangle*2 <- Edges <- Children]; + /// let mut iter = map.all_elementary_decompositions(); + /// assert_eq!( + /// iter.next(), + /// Some(((Elementary::new_edges(Triangle), 1), elementaries![Line*6 <- Children]))); + /// assert_eq!( + /// iter.next(), + /// Some(( + /// (Elementary::new_children(Triangle), 1), + /// elementaries![Triangle*8 <- Edges <- Take([3, 6, 1, 7, 2, 5], 12)], + /// )) + /// ); + /// assert_eq!(iter.next(), None); + /// ``` + fn all_elementary_decompositions<'a>( + &'a self, + ) -> Box + 'a>; } -impl SplitOuterElementary for Vec { - type Output = Vec<((Elementary, usize), Self)>; - - fn split_outer_elementary(&self) -> Self::Output { +impl AllElementaryDecompositions for Vec { + fn all_elementary_decompositions<'a>( + &'a self, + ) -> Box + 'a> { let mut splits = Vec::new(); for (i, item) in self.iter().enumerate().rev() { - if let Some((outer, mut inner)) = (&self[..i]).shift_inner_to_outer(item, 1) { - inner.extend(self[i + 1..].iter().cloned()); + if let Some((outer, middle)) = (&self[i + 1..]).swap_elementary_composition(item, 1) { + let inner = self[..i].iter().cloned().chain(middle).collect(); splits.push((outer, inner)); } if let Elementary::Edges(Offset(Edges(Simplex::Line), offset)) = item { let mut children = Elementary::new_children(Simplex::Line); children.add_offset(*offset); - if let Some((outer, mut inner)) = (&self[..i]).shift_inner_to_outer(&children, 1) { - inner.push(item.clone()); + if let Some((outer, middle)) = + (&self[i + 1..]).swap_elementary_composition(&children, 1) + { + let mut inner = self[..i].to_vec(); inner.push(Elementary::new_take( Simplex::Line.swap_edges_children_map(), Simplex::Line.nedges() * Simplex::Line.nchildren(), )); - inner.extend(self[i + 1..].iter().cloned()); + inner.push(item.clone()); + inner.extend(middle); splits.push((outer, inner)); } } } - splits + Box::new(splits.into_iter()) } } -impl SplitOuterElementary for WithBounds { - // TODO: As soons as we can use `impl Iterator`, drop the collect. - type Output = Vec<((Elementary, usize), Self)>; - - fn split_outer_elementary(&self) -> Self::Output { - self.unbounded() - .split_outer_elementary() - .into_iter() - .map(|(outer, unbounded)| { - ( - outer, - WithBounds::new_unchecked(unbounded, self.dim_in(), self.len_in()), - ) - }) - .collect() +impl AllElementaryDecompositions for WithBounds { + fn all_elementary_decompositions<'a>( + &'a self, + ) -> Box + 'a> { + Box::new( + self.unbounded() + .all_elementary_decompositions() + .into_iter() + .map(|(outer, unbounded)| { + ( + outer, + WithBounds::new_unchecked(unbounded, self.dim_in(), self.len_in()), + ) + }), + ) } } +#[macro_export] +macro_rules! elementaries { + (Point*$len_out:literal $($tail:tt)*) => {{ + use $crate::elementary::{Elementary, WithBounds}; + let mut comp: Vec = Vec::new(); + $crate::elementaries!{@adv comp, Point; $($tail)*} + comp.reverse(); + WithBounds::from_output(comp, 0, $len_out).unwrap() + }}; + ($simplex:tt*$len_out:literal $($tail:tt)*) => {{ + use $crate::elementary::{Elementary, WithBounds}; + let mut comp: Vec = Vec::new(); + $crate::elementaries!{@adv comp, $simplex; $($tail)*} + comp.reverse(); + WithBounds::from_output(comp, $simplex.dim(), $len_out).unwrap() + }}; + (@adv $comp:ident, $simplex:ident;) => {}; + (@adv $comp:ident, $simplex:ident; <- Children $($tail:tt)*) => {{ + $comp.push(Elementary::new_children($crate::simplex::Simplex::$simplex)); + $crate::elementaries!{@adv $comp, $simplex; $($tail)*} + }}; + (@adv $comp:ident, Triangle; <- Edges $($tail:tt)*) => {{ + $comp.push(Elementary::new_edges($crate::simplex::Simplex::Triangle)); + $crate::elementaries!{@adv $comp, Line; $($tail)*} + }}; + (@adv $comp:ident, Line; <- Edges $($tail:tt)*) => {{ + $comp.push(Elementary::new_edges($crate::simplex::Simplex::Line)); + $crate::elementaries!{@adv $comp, Point; $($tail)*} + }}; + (@adv $comp:ident, $simplex:ident; <- Transpose($len1:expr, $len2:expr) $($tail:tt)*) => {{ + $comp.push(Elementary::new_transpose($len1, $len2)); + $crate::elementaries!{@adv $comp, $simplex; $($tail)*} + }}; + (@adv $comp:ident, $simplex:ident; <- Take($indices:expr, $len:expr) $($tail:tt)*) => {{ + $comp.push(Elementary::new_take($indices.to_vec(), $len)); + $crate::elementaries!{@adv $comp, $simplex; $($tail)*} + }}; +} + #[cfg(test)] mod tests { use super::*; @@ -1226,10 +1294,10 @@ mod tests { }}; } - macro_rules! assert_shift_inner_to_outer { + macro_rules! assert_swap_elementary_composition { ($($item:expr),*; $($simplex:ident),*) => {{ let unshifted = [$(Elementary::from($item),)*]; - let ((litem, lstride), lchain) = (&unshifted[1..]).shift_inner_to_outer(&unshifted[0], 1).unwrap(); + let ((litem, lstride), lchain) = (&unshifted[1..]).swap_elementary_composition(&unshifted[0], 1).unwrap(); let mut shifted: Vec = Vec::new(); shifted.extend(lchain.into_iter()); let litem_mod_out = litem.mod_out(); @@ -1242,31 +1310,31 @@ mod tests { } #[test] - fn shift_inner_to_outer() { - assert_shift_inner_to_outer!( + fn swap_elementary_composition() { + assert_swap_elementary_composition!( Elementary::new_take(vec![0, 1], 3), Transpose::new(4, 3); ); - assert_shift_inner_to_outer!( + assert_swap_elementary_composition!( Elementary::new_take(vec![0, 1], 3), Transpose::new(5, 4*3), Transpose::new(3, 5); ); - assert_shift_inner_to_outer!( + assert_swap_elementary_composition!( Elementary::new_take(vec![0, 1], 3), Transpose::new(5*4, 3), Transpose::new(5, 4); ); - assert_shift_inner_to_outer!( + assert_swap_elementary_composition!( Elementary::new_children(Line), {let mut elem = Elementary::new_children(Line); elem.add_offset(1); elem}; Line, Line ); - assert_shift_inner_to_outer!( + assert_swap_elementary_composition!( Elementary::new_children(Line), Elementary::new_edges(Line); Line ); - assert_shift_inner_to_outer!( + assert_swap_elementary_composition!( Elementary::new_children(Line), {let mut elem = Elementary::new_edges(Line); elem.add_offset(1); elem}; Line ); - assert_shift_inner_to_outer!( + assert_swap_elementary_composition!( Elementary::new_children(Line), Elementary::new_edges(Triangle); Line ); diff --git a/src/ops.rs b/src/ops.rs index 31fb334ee..979090920 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -661,43 +661,11 @@ impl FromIterator for UniformProduct> { mod tests { use super::*; use crate::assert_map_apply; - use crate::elementary::{Elementary, WithBounds}; + use crate::elementaries; use crate::simplex::Simplex::*; use approx::assert_abs_diff_eq; use std::iter; - macro_rules! elementaries { - (Point*$len_out:literal $($tail:tt)*) => {{ - let mut comp: Vec = Vec::new(); - elementaries!{@push comp, Point; $($tail)*} - comp.reverse(); - WithBounds::from_output(comp, 0, $len_out).unwrap() - }}; - ($simplex:ident*$len_out:literal $($tail:tt)*) => {{ - let mut comp = Vec::new(); - elementaries!{@push comp, $simplex; $($tail)*} - comp.reverse(); - WithBounds::from_output(comp, $simplex.dim(), $len_out).unwrap() - }}; - (@push $comp:ident, $simplex:expr;) => {}; - (@push $comp:ident, $simplex:expr; <- Children $($tail:tt)*) => {{ - $comp.push(Elementary::new_children($simplex)); - elementaries!{@push $comp, $simplex; $($tail)*} - }}; - (@push $comp:ident, $simplex:expr; <- Edges $($tail:tt)*) => {{ - $comp.push(Elementary::new_edges($simplex)); - elementaries!{@push $comp, $simplex.edge_simplex(); $($tail)*} - }}; - (@push $comp:ident, $simplex:expr; <- Transpose($len1:expr, $len2:expr) $($tail:tt)*) => {{ - $comp.push(Elementary::new_transpose($len1, $len2)); - elementaries!{@push $comp, $simplex; $($tail)*} - }}; - (@push $comp:ident, $simplex:expr; <- Take($indices:expr, $len:expr) $($tail:tt)*) => {{ - $comp.push(Elementary::new_take($indices.to_vec(), $len)); - elementaries!{@push $comp, $simplex; $($tail)*} - }}; - } - #[test] fn uniform_composition1() { let map = UniformComposition::new(vec![elementaries![Point * 1]]).unwrap(); diff --git a/src/relative.rs b/src/relative.rs index 6690161c3..a51586d89 100644 --- a/src/relative.rs +++ b/src/relative.rs @@ -1,127 +1,132 @@ use crate::elementary::{ - Edges, Elementary, Identity, Offset, ShiftInnerToOuter, SplitOuterElementary, Transpose, - UnboundedMap, WithBounds, + AllElementaryDecompositions, Elementary, SwapElementaryComposition, UnboundedMap, WithBounds, }; -use crate::ops::{BinaryComposition, UniformProduct, BinaryProduct}; -use crate::simplex::Simplex; +use crate::ops::{BinaryComposition, BinaryProduct, UniformProduct}; use crate::util::ReplaceNthIter as _; -use crate::{AddOffset, Map, UnapplyIndicesData}; +use crate::{AddOffset, Map}; use std::collections::BTreeMap; -use std::iter; -use std::ops::Deref; -use std::rc::Rc; -impl SplitOuterElementary for UniformProduct> +impl AllElementaryDecompositions for UniformProduct> where - M: Map + SplitOuterElementary + Clone, + M: Map + AllElementaryDecompositions + Clone, { - type Output = Vec<((Elementary, usize), Self)>; - - fn split_outer_elementary(&self) -> Self::Output { - self.iter() - .enumerate() - .zip(self.offsets_out()) - .zip(self.strides_out()) - .flat_map(|(((iprod, item), prod_offset), prod_stride)| { - item.split_outer_elementary().into_iter().map( - move |((mut elmtry, mut stride), inner)| { - elmtry.add_offset(prod_offset); - stride *= prod_stride; - let product = self.iter().cloned().replace_nth(iprod, inner).collect(); - ((elmtry, stride), product) - }, - ) - }) - .collect() + fn all_elementary_decompositions<'a>( + &'a self, + ) -> Box + 'a> { + Box::new( + self.iter() + .enumerate() + .zip(self.offsets_out()) + .zip(self.strides_out()) + .flat_map(move |(((iprod, term), prod_offset), prod_stride)| { + term.all_elementary_decompositions().into_iter().map( + move |((mut elmtry, mut stride), inner)| { + elmtry.add_offset(prod_offset); + if elmtry.mod_out() == 1 { + stride = 1; + } else { + stride *= prod_stride; + } + let product = self.iter().cloned().replace_nth(iprod, inner).collect(); + ((elmtry, stride), product) + }, + ) + }), + ) } } -impl SplitOuterElementary for BinaryProduct +impl AllElementaryDecompositions for BinaryProduct where - M0: Map + SplitOuterElementary + Clone, - M1: Map + SplitOuterElementary + Clone, + M0: Map + AllElementaryDecompositions + Clone, + M1: Map + AllElementaryDecompositions + Clone, { - type Output = Vec<((Elementary, usize), Self)>; - - fn split_outer_elementary(&self) -> Self::Output { - let first = self.first().split_outer_elementary().into_iter().map( - |((elmtry, mut stride), first)| { + fn all_elementary_decompositions<'a>( + &'a self, + ) -> Box + 'a> { + let first = self + .first() + .all_elementary_decompositions() + .into_iter() + .map(|((elmtry, mut stride), first)| { stride *= self.second().len_out(); let product = BinaryProduct::new(first, self.second().clone()); ((elmtry, stride), product) }); - let second = self.second().split_outer_elementary().into_iter().map( - |((mut elmtry, stride), second)| { + let second = self + .second() + .all_elementary_decompositions() + .into_iter() + .map(|((mut elmtry, stride), second)| { elmtry.add_offset(self.first().dim_out()); let product = BinaryProduct::new(self.first().clone(), second); ((elmtry, stride), product) }); - first.chain(second).collect() + Box::new(first.chain(second)) } } -// TODO: BinaryProduct // TODO: UniformComposition? -impl SplitOuterElementary for BinaryComposition +impl AllElementaryDecompositions for BinaryComposition where - Inner: Map + SplitOuterElementary + Clone, - Outer: Map + SplitOuterElementary + ShiftInnerToOuter, + Inner: Map + AllElementaryDecompositions + Clone, + Outer: Map + AllElementaryDecompositions + SwapElementaryComposition, { - type Output = Vec<((Elementary, usize), Self)>; - - fn split_outer_elementary(&self) -> Self::Output { - let mut split: Vec<_> = self + fn all_elementary_decompositions<'a>( + &'a self, + ) -> Box + 'a> { + let split_outer = self .outer() - .split_outer_elementary() + .all_elementary_decompositions() .into_iter() - .map(|(out, outer)| { - ( - out, - BinaryComposition::new(self.inner().clone(), outer).unwrap(), - ) - }) - .collect(); - split.extend( - self.inner() - .split_outer_elementary() - .into_iter() - .filter_map(|((elmtry, stride), inner)| { - self.outer() - .shift_inner_to_outer(&elmtry, stride) - .map(|(out, outer)| (out, Self::new(inner, outer).unwrap())) - }), - ); - split + .map(|(elmtry, outer)| (elmtry, Self::new(self.inner().clone(), outer).unwrap())); + let split_inner = self + .inner() + .all_elementary_decompositions() + .into_iter() + .filter_map(|((elmtry, stride), inner)| { + self.outer() + .swap_elementary_composition(&elmtry, stride) + .map(|(elmtry, outer)| (elmtry, Self::new(inner, outer).unwrap())) + }); + Box::new(split_outer.chain(split_inner)) } } -pub fn remove_common_outer(mut map1: M1, mut map2: M2) -> (Vec, M1, M2) +/// Decompose two maps into two remainders and a common map. +/// +/// The decomposition is such that the composition of the remainder with the +/// common part gives a map that is equivalent to the original. +pub fn decompose_common(mut map1: M1, mut map2: M2) -> (M1, M2, WithBounds>) where - M1: Map + SplitOuterElementary, - M2: Map + SplitOuterElementary, + M1: Map + AllElementaryDecompositions, + M2: Map + AllElementaryDecompositions, { + // TODO: check output dimensions? and return error if dimensions don't match? + assert_eq!(map1.len_out(), map2.len_out()); + assert_eq!(map1.dim_out(), map2.dim_out()); let mut common = Vec::new(); while !map1.is_identity() && !map2.is_identity() { - let mut outers2: BTreeMap<_, _> = map2.split_outer_elementary().into_iter().collect(); - if let Some(((outer, stride), new_map1, new_map2)) = map1 - .split_outer_elementary() - .into_iter() + let mut outers2: BTreeMap<_, _> = map2.all_elementary_decompositions().collect(); + (map1, map2) = if let Some(((outer, stride), map1, map2)) = map1 + .all_elementary_decompositions() .filter_map(|(key, t1)| outers2.remove(&key).map(|t2| (key, t1, t2))) .next() { + let outer_mod_out = outer.mod_out(); + common.push(outer); if stride != 1 { - common.push(Elementary::new_transpose(stride, outer.mod_out())); + common.push(Elementary::new_transpose(stride, outer_mod_out)); } - common.push(outer); - map1 = new_map1; - map2 = new_map2; - continue; + (map1, map2) + } else { + break; } - break; } common.reverse(); - (common, map1, map2) + let common = WithBounds::from_input(common, map1.dim_out(), map1.len_out()).unwrap(); + (map1, map2, common) } //#[derive(Debug, Clone, PartialEq)] @@ -196,7 +201,7 @@ where // fn relative_to(&self, target: &Self) -> Option { // let (_, rem, rel) = target // .get_unbounded() -// .remove_common_prefix_opt_lhs(self.get_unbounded()); +// .split_common_prefix_opt_lhs(self.get_unbounded()); // rem.is_identity() // .then(|| Relative::Single(Self::new_unchecked(rel, target.dim_in(), target.len_in()))) // } @@ -326,7 +331,7 @@ where // for target in targets.iter() { // let (_, rem, rel) = target // .get_unbounded() -// .remove_common_prefix_opt_lhs(self.get_unbounded()); +// .split_common_prefix_opt_lhs(self.get_unbounded()); // if rem.is_identity() { // let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); // let rel: Vec = iter::once(slice).chain(rel).collect(); @@ -387,86 +392,94 @@ where // } //} // -//#[cfg(test)] -//mod tests { -// use super::*; -// use crate::elementary::*; -// use crate::ops::Composition; -// use crate::simplex::Simplex::*; -// use approx::assert_abs_diff_eq; -// use std::iter; -// -// #[test] -// fn remove_common_prefix() { -// let c1 = Elementary::new_children(Line); -// let e1 = Elementary::new_edges(Line); -// let swap_ec1 = Elementary::new_take(vec![2, 1], 4); -// let a = vec![c1.clone(), c1.clone()]; -// let b = vec![e1.clone()]; -// assert_eq!( -// a.remove_common_prefix(&b), -// ( -// vec![c1.clone(), c1.clone()], -// vec![], -// vec![e1.clone(), swap_ec1.clone(), swap_ec1.clone()], -// ) -// ); -// } -// -// macro_rules! single { -// (dim=$dim_out:literal, len=$len_out:literal) => { -// WithBounds::>::new(Vec::new(), $dim_out, $len_out).unwrap() -// }; -// (dim=$dim_out:literal, len=$len_out:literal <- $($item:expr),*) => { -// WithBounds::>::new(vec![$(Elementary::from($item)),*], $dim_out, $len_out).unwrap() -// }; -// } -// -// macro_rules! assert_equiv_maps { -// ($a:expr, $b:expr $(, $simplex:ident)*) => {{ -// let a = $a; -// let b = $b; -// println!("a: {a:?}"); -// println!("b: {b:?}"); -// // Build coords: the outer product of the vertices of the given simplices, zero-padded -// // to the dimension of the root. -// let coords = iter::once([]); -// let simplex_dim = 0; -// $( -// let coords = coords.flat_map(|coord| { -// $simplex -// .vertices() -// .chunks($simplex.dim()) -// .map(move |vert| [&coord, vert].concat()) -// }); -// let simplex_dim = simplex_dim + $simplex.dim(); -// )* -// assert_eq!(simplex_dim, a.dim_in(), "the given simplices don't add up to the input dimension"); -// let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); -// let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); -// // Test if every input maps to the same output for both `a` and `b`. -// for i in 0..2 * a.len_in() { -// let mut crds_a = coords.clone(); -// let mut crds_b = coords.clone(); -// let ja = a.apply_inplace(i, &mut crds_a, a.dim_out(), 0); -// let jb = b.apply_inplace(i, &mut crds_b, a.dim_out(), 0); -// assert_eq!(ja, jb, "i={i}"); -// assert_abs_diff_eq!(crds_a[..], crds_b[..]); -// } -// }}; -// } -// -// #[test] -// fn rel_to() { -// let a1 = single!(dim=1, len=2 <- Children::new(Line), Take::new(vec![0, 2], 4)); -// let a2 = single!(dim=1, len=2 <- Children::new(Line), Take::new(vec![1, 3], 4), Children::new(Line)); -// let a = Concatenation::new(vec![a1, a2]); -// let b = -// single!(dim=1, len=2 <- Children::new(Line), Children::new(Line), Children::new(Line)); -// assert_equiv_maps!( -// Composition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), -// b, -// Line -// ); -// } -//} +#[cfg(test)] +mod tests { + use super::*; + use crate::elementaries; + use crate::simplex::Simplex::*; + use approx::assert_abs_diff_eq; + use std::iter; + + macro_rules! assert_equiv_maps { + ($a:expr, $b:expr $(, $simplex:ident)*) => {{ + let a = $a; + let b = $b; + println!("a: {a:?}"); + println!("b: {b:?}"); + // Build coords: the outer product of the vertices of the given simplices, zero-padded + // to the dimension of the root. + let coords = iter::once([]); + let simplex_dim = 0; + $( + let coords = coords.flat_map(|coord| { + $simplex + .vertices() + .chunks($simplex.dim()) + .map(move |vert| [&coord, vert].concat()) + }); + let simplex_dim = simplex_dim + $simplex.dim(); + )* + assert_eq!(simplex_dim, a.dim_in(), "the given simplices don't add up to the input dimension"); + let pad: Vec = iter::repeat(0.0).take(a.delta_dim()).collect(); + let coords: Vec = coords.flat_map(|coord| [&coord[..], &pad].concat()).collect(); + // Test if every input maps to the same output for both `a` and `b`. + for i in 0..2 * a.len_in() { + let mut crds_a = coords.clone(); + let mut crds_b = coords.clone(); + let ja = a.apply_inplace(i, &mut crds_a, a.dim_out(), 0); + let jb = b.apply_inplace(i, &mut crds_b, a.dim_out(), 0); + assert_eq!(ja, jb, "i={i}"); + assert_abs_diff_eq!(crds_a[..], crds_b[..]); + } + }}; + } + + #[test] + fn decompose_common_vec() { + let map1 = elementaries![Line*2 <- Children <- Children]; + let map2 = elementaries![Line*2 <- Children <- Take([0, 2], 4)]; + assert_eq!( + decompose_common(map1, map2), + ( + elementaries![Line*4 <- Children], + elementaries![Line*4 <- Take([0, 2], 4)], + elementaries![Line*2 <- Children], + ) + ); + } + + #[test] + fn decompose_common_product() { + let map1 = elementaries![Line*2 <- Children <- Children]; + let map2 = elementaries![Line*2 <- Children <- Take([0, 2], 4)]; + let (rel1, rel2, common) = decompose_common(map1.clone(), map2.clone()); + assert!(!common.is_identity()); + assert_equiv_maps!( + BinaryComposition::new(rel1.clone(), common.clone()).unwrap(), + map1.clone(), + Line + ); + assert_equiv_maps!( + BinaryComposition::new(rel2.clone(), common.clone()).unwrap(), + map2.clone(), + Line + ); + //assert_eq!(rel1, BinaryProduct::new(elementaries![Line*4 <- Children], elementaries![Line*4 <- Take([0, 2], 4)])); + //assert_eq!(rel2, BinaryProduct::new(elementaries![Line*4 <- Take([0, 2], 4)], elementaries![Line*4 <- Children])); + //assert_eq!(common, elementaries![Line*2]); + // WithBounds { map: [Offset(Children(Line), 1), Transpose(2, 1), Offset(Children(Line), 0)], dim_in: 2, delta_dim: 0, len_in: 16, len_out: 4 } + } + // + // #[test] + // fn rel_to() { + // let a1 = elementaries![Line*2 <- Children <- Take([0, 2], 4)]; + // let a2 = elementaries![Line*2 <- Children <- Take([1, 3], 4) <- Children]; + // let a = BinaryConcat::new(a1, a2).unwrap(); + // let b = elementaries![Line*2 <- Children <- Children <- Children]; + // assert_equiv_maps!( + // BinaryComposition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), + // b, + // Line + // ); + // } +} From d108758228b8e50f93379b5121dfc26aaad7abbe Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Fri, 1 Jul 2022 14:08:41 +0200 Subject: [PATCH 29/45] WIP --- src/elementary.rs | 62 +++++++++++++++++++++++++---------------------- src/ops.rs | 56 +++++++++++++++++++++--------------------- src/relative.rs | 24 +++++++++--------- 3 files changed, 74 insertions(+), 68 deletions(-) diff --git a/src/elementary.rs b/src/elementary.rs index b84316ed6..77e340d2a 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -685,6 +685,7 @@ where #[inline] fn dim_in(&self) -> usize { self.iter() + .rev() .fold((0, 0), |(o, i), map| comp_dim_out_in(map, o, i)) .1 } @@ -695,12 +696,14 @@ where #[inline] fn mod_in(&self) -> usize { self.iter() + .rev() .fold((1, 1), |(o, i), map| comp_mod_out_in(map, o, i)) .1 } #[inline] fn mod_out(&self) -> usize { self.iter() + .rev() .fold((1, 1), |(o, i), map| comp_mod_out_in(map, o, i)) .0 } @@ -712,22 +715,25 @@ where stride: usize, offset: usize, ) -> usize { - self.iter().fold(index, |index, map| { + self.iter().rev().fold(index, |index, map| { map.apply_inplace(index, coords, stride, offset) }) } #[inline] fn apply_index(&self, index: usize) -> usize { - self.iter().fold(index, |index, map| map.apply_index(index)) + self.iter() + .rev() + .fold(index, |index, map| map.apply_index(index)) } #[inline] fn apply_indices_inplace(&self, indices: &mut [usize]) { self.iter() + .rev() .for_each(|map| map.apply_indices_inplace(indices)); } #[inline] fn unapply_indices(&self, indices: &[T]) -> Vec { - self.iter().rev().fold(indices.to_vec(), |indices, map| { + self.iter().fold(indices.to_vec(), |indices, map| { map.unapply_indices(&indices) }) } @@ -838,12 +844,11 @@ impl Map for WithBounds { } } -/// An interface for swapping a composition of an [`Elementary`] with `Self`. +/// An interface for swapping a composition of `Self` with an [`Elementary`]. pub trait SwapElementaryComposition { type Output; - /// Returns a map and an [`Elementary`] such that the composition is equivalent to - /// the composition of `inner` and `self`. + /// Returns an [`Elementary`'] and a map such that the composition those is equivalent to the composition of `self` with `inner`. fn swap_elementary_composition( &self, inner: &Elementary, @@ -867,7 +872,7 @@ impl SwapElementaryComposition for [Elementary] { let mut queue: Vec = Vec::new(); let mut stride_out = stride; let mut stride_in = stride; - for mut item in self.iter().cloned() { + for mut item in self.iter().rev().cloned() { // Swap matching edges and children at the same offset. if let Elementary::Edges(Offset(Edges(esimplex), eoffset)) = &item { if let Elementary::Children(Offset(Children(ref mut csimplex), coffset)) = @@ -947,6 +952,7 @@ impl SwapElementaryComposition for [Elementary] { if target.mod_out() == 1 { stride_out = 1; } + shifted_items.reverse(); Some(((target, stride_out), shifted_items)) } } @@ -1020,24 +1026,23 @@ impl AllElementaryDecompositions for Vec { &'a self, ) -> Box + 'a> { let mut splits = Vec::new(); - for (i, item) in self.iter().enumerate().rev() { - if let Some((outer, middle)) = (&self[i + 1..]).swap_elementary_composition(item, 1) { - let inner = self[..i].iter().cloned().chain(middle).collect(); + for (i, item) in self.iter().enumerate() { + if let Some((outer, mut inner)) = (&self[..i]).swap_elementary_composition(item, 1) { + inner.extend(self[i + 1..].iter().cloned()); splits.push((outer, inner)); } if let Elementary::Edges(Offset(Edges(Simplex::Line), offset)) = item { let mut children = Elementary::new_children(Simplex::Line); children.add_offset(*offset); - if let Some((outer, middle)) = - (&self[i + 1..]).swap_elementary_composition(&children, 1) + if let Some((outer, mut inner)) = + (&self[..i]).swap_elementary_composition(&children, 1) { - let mut inner = self[..i].to_vec(); + inner.push(item.clone()); inner.push(Elementary::new_take( Simplex::Line.swap_edges_children_map(), Simplex::Line.nedges() * Simplex::Line.nchildren(), )); - inner.push(item.clone()); - inner.extend(middle); + inner.extend(self[i + 1..].iter().cloned()); splits.push((outer, inner)); } } @@ -1068,16 +1073,16 @@ impl AllElementaryDecompositions macro_rules! elementaries { (Point*$len_out:literal $($tail:tt)*) => {{ use $crate::elementary::{Elementary, WithBounds}; + #[allow(unused_mut)] let mut comp: Vec = Vec::new(); $crate::elementaries!{@adv comp, Point; $($tail)*} - comp.reverse(); WithBounds::from_output(comp, 0, $len_out).unwrap() }}; ($simplex:tt*$len_out:literal $($tail:tt)*) => {{ use $crate::elementary::{Elementary, WithBounds}; + #[allow(unused_mut)] let mut comp: Vec = Vec::new(); $crate::elementaries!{@adv comp, $simplex; $($tail)*} - comp.reverse(); WithBounds::from_output(comp, $simplex.dim(), $len_out).unwrap() }}; (@adv $comp:ident, $simplex:ident;) => {}; @@ -1297,14 +1302,13 @@ mod tests { macro_rules! assert_swap_elementary_composition { ($($item:expr),*; $($simplex:ident),*) => {{ let unshifted = [$(Elementary::from($item),)*]; - let ((litem, lstride), lchain) = (&unshifted[1..]).swap_elementary_composition(&unshifted[0], 1).unwrap(); + let ((litem, lstride), lchain) = (&unshifted[..unshifted.len() - 1]).swap_elementary_composition(&unshifted.last().unwrap(), 1).unwrap(); let mut shifted: Vec = Vec::new(); - shifted.extend(lchain.into_iter()); - let litem_mod_out = litem.mod_out(); - shifted.push(litem); if lstride != 1 { - shifted.push(Elementary::new_transpose(lstride, litem_mod_out)); + shifted.push(Elementary::new_transpose(lstride, litem.mod_out())); } + shifted.push(litem); + shifted.extend(lchain.into_iter()); assert_equiv_maps!(&shifted[..], &unshifted[..] $(, $simplex)*); }}; } @@ -1312,21 +1316,21 @@ mod tests { #[test] fn swap_elementary_composition() { assert_swap_elementary_composition!( - Elementary::new_take(vec![0, 1], 3), Transpose::new(4, 3); + Transpose::new(4, 3), Elementary::new_take(vec![0, 1], 3); ); assert_swap_elementary_composition!( - Elementary::new_take(vec![0, 1], 3), Transpose::new(5, 4*3), Transpose::new(3, 5); + Transpose::new(3, 5), Transpose::new(5, 4*3), Elementary::new_take(vec![0, 1], 3); ); assert_swap_elementary_composition!( - Elementary::new_take(vec![0, 1], 3), Transpose::new(5*4, 3), Transpose::new(5, 4); + Transpose::new(5, 4), Transpose::new(5*4, 3), Elementary::new_take(vec![0, 1], 3); ); assert_swap_elementary_composition!( - Elementary::new_children(Line), - {let mut elem = Elementary::new_children(Line); elem.add_offset(1); elem}; + {let mut elem = Elementary::new_children(Line); elem.add_offset(1); elem}, + Elementary::new_children(Line); Line, Line ); assert_swap_elementary_composition!( - Elementary::new_children(Line), Elementary::new_edges(Line); + Elementary::new_edges(Line), Elementary::new_children(Line); Line ); assert_swap_elementary_composition!( @@ -1335,7 +1339,7 @@ mod tests { Line ); assert_swap_elementary_composition!( - Elementary::new_children(Line), Elementary::new_edges(Triangle); + Elementary::new_edges(Triangle), Elementary::new_children(Line); Line ); } diff --git a/src/ops.rs b/src/ops.rs index 979090920..fe8e3469c 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -3,30 +3,30 @@ use num::Integer as _; use std::ops::Deref; #[derive(Debug, Clone, PartialEq)] -pub struct BinaryComposition(Inner, Outer); +pub struct BinaryComposition(Outer, Inner); -impl BinaryComposition { - pub fn new(inner: Inner, outer: Outer) -> Result { +impl BinaryComposition { + pub fn new(outer: Outer, inner: Inner) -> Result { if inner.dim_out() != outer.dim_in() { Err(Error::DimensionMismatch) } else if inner.len_out() != outer.len_in() { Err(Error::LengthMismatch) } else { - Ok(Self(inner, outer)) + Ok(Self(outer, inner)) } } - pub fn inner(&self) -> &Inner { + pub fn outer(&self) -> &Outer { &self.0 } - pub fn outer(&self) -> &Outer { + pub fn inner(&self) -> &Inner { &self.1 } } -impl Map for BinaryComposition { +impl Map for BinaryComposition { #[inline] fn dim_in(&self) -> usize { - self.0.dim_in() + self.1.dim_in() } #[inline] fn delta_dim(&self) -> usize { @@ -34,11 +34,11 @@ impl Map for BinaryComposition { } #[inline] fn len_in(&self) -> usize { - self.0.len_in() + self.1.len_in() } #[inline] fn len_out(&self) -> usize { - self.1.len_out() + self.0.len_out() } #[inline] fn apply_inplace_unchecked( @@ -49,25 +49,25 @@ impl Map for BinaryComposition { offset: usize, ) -> usize { let index = self - .0 + .1 .apply_inplace_unchecked(index, coords, stride, offset); - self.1 + self.0 .apply_inplace_unchecked(index, coords, stride, offset) } #[inline] fn apply_index_unchecked(&self, index: usize) -> usize { - self.1 - .apply_index_unchecked(self.0.apply_index_unchecked(index)) + self.0 + .apply_index_unchecked(self.1.apply_index_unchecked(index)) } #[inline] fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.0.apply_indices_inplace_unchecked(indices); self.1.apply_indices_inplace_unchecked(indices); + self.0.apply_indices_inplace_unchecked(indices); } #[inline] fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - self.0 - .unapply_indices_unchecked(&self.1.unapply_indices_unchecked(indices)) + self.1 + .unapply_indices_unchecked(&self.0.unapply_indices_unchecked(indices)) } #[inline] fn is_identity(&self) -> bool { @@ -87,7 +87,7 @@ where Array: Deref, { pub fn new(array: Array) -> Result { - let mut iter = array.iter(); + let mut iter = array.iter().rev(); if let Some(map) = iter.next() { let mut dim_out = map.dim_out(); let mut len_out = map.len_out(); @@ -120,11 +120,11 @@ where { #[inline] fn dim_in(&self) -> usize { - self.0.first().unwrap().dim_in() + self.0.last().unwrap().dim_in() } #[inline] fn dim_out(&self) -> usize { - self.0.last().unwrap().dim_out() + self.0.first().unwrap().dim_out() } #[inline] fn delta_dim(&self) -> usize { @@ -132,11 +132,11 @@ where } #[inline] fn len_in(&self) -> usize { - self.0.first().unwrap().len_in() + self.0.last().unwrap().len_in() } #[inline] fn len_out(&self) -> usize { - self.0.last().unwrap().len_out() + self.0.first().unwrap().len_out() } #[inline] fn apply_inplace_unchecked( @@ -146,23 +146,25 @@ where stride: usize, offset: usize, ) -> usize { - self.iter().fold(index, |index, map| { + self.iter().rev().fold(index, |index, map| { map.apply_inplace_unchecked(index, coords, stride, offset) }) } #[inline] fn apply_index_unchecked(&self, index: usize) -> usize { self.iter() + .rev() .fold(index, |index, map| map.apply_index_unchecked(index)) } #[inline] fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { self.iter() + .rev() .for_each(|map| map.apply_indices_inplace_unchecked(indices)); } #[inline] fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { - let mut iter = self.iter().rev(); + let mut iter = self.iter(); let indices = iter.next().unwrap().unapply_indices_unchecked(indices); iter.fold(indices, |indices, map| { map.unapply_indices_unchecked(&indices) @@ -680,8 +682,8 @@ mod tests { #[test] fn uniform_composition2() { let map = UniformComposition::new(vec![ - elementaries![Point*12 <- Take([1, 0], 3)], elementaries![Point*12 <- Transpose(4, 3)], + elementaries![Point*12 <- Take([1, 0], 3)], ]) .unwrap(); assert_eq!(map.len_in(), 8); @@ -702,9 +704,9 @@ mod tests { #[test] fn uniform_composition3() { let map = UniformComposition::new(vec![ - elementaries![Line*4 <- Edges], - elementaries![Line*2 <- Children], elementaries![Line*1 <- Children], + elementaries![Line*2 <- Children], + elementaries![Line*4 <- Edges], ]) .unwrap(); assert_eq!(map.len_in(), 8); diff --git a/src/relative.rs b/src/relative.rs index a51586d89..e6d92d496 100644 --- a/src/relative.rs +++ b/src/relative.rs @@ -68,10 +68,10 @@ where // TODO: UniformComposition? -impl AllElementaryDecompositions for BinaryComposition +impl AllElementaryDecompositions for BinaryComposition where - Inner: Map + AllElementaryDecompositions + Clone, Outer: Map + AllElementaryDecompositions + SwapElementaryComposition, + Inner: Map + AllElementaryDecompositions + Clone, { fn all_elementary_decompositions<'a>( &'a self, @@ -80,7 +80,7 @@ where .outer() .all_elementary_decompositions() .into_iter() - .map(|(elmtry, outer)| (elmtry, Self::new(self.inner().clone(), outer).unwrap())); + .map(|(elmtry, outer)| (elmtry, Self::new(outer, self.inner().clone()).unwrap())); let split_inner = self .inner() .all_elementary_decompositions() @@ -88,7 +88,7 @@ where .filter_map(|((elmtry, stride), inner)| { self.outer() .swap_elementary_composition(&elmtry, stride) - .map(|(elmtry, outer)| (elmtry, Self::new(inner, outer).unwrap())) + .map(|(elmtry, outer)| (elmtry, Self::new(outer, inner).unwrap())) }); Box::new(split_outer.chain(split_inner)) } @@ -96,9 +96,9 @@ where /// Decompose two maps into two remainders and a common map. /// -/// The decomposition is such that the composition of the remainder with the -/// common part gives a map that is equivalent to the original. -pub fn decompose_common(mut map1: M1, mut map2: M2) -> (M1, M2, WithBounds>) +/// The decomposition is such that the composition the common part with the +/// remainder gives a map that is equivalent to the original. +pub fn decompose_common(mut map1: M1, mut map2: M2) -> (WithBounds>, M1, M2) where M1: Map + AllElementaryDecompositions, M2: Map + AllElementaryDecompositions, @@ -126,7 +126,7 @@ where } common.reverse(); let common = WithBounds::from_input(common, map1.dim_out(), map1.len_out()).unwrap(); - (map1, map2, common) + (common, map1, map2) } //#[derive(Debug, Clone, PartialEq)] @@ -441,9 +441,9 @@ mod tests { assert_eq!( decompose_common(map1, map2), ( + elementaries![Line*2 <- Children], elementaries![Line*4 <- Children], elementaries![Line*4 <- Take([0, 2], 4)], - elementaries![Line*2 <- Children], ) ); } @@ -452,15 +452,15 @@ mod tests { fn decompose_common_product() { let map1 = elementaries![Line*2 <- Children <- Children]; let map2 = elementaries![Line*2 <- Children <- Take([0, 2], 4)]; - let (rel1, rel2, common) = decompose_common(map1.clone(), map2.clone()); + let (common, rel1, rel2) = decompose_common(map1.clone(), map2.clone()); assert!(!common.is_identity()); assert_equiv_maps!( - BinaryComposition::new(rel1.clone(), common.clone()).unwrap(), + BinaryComposition::new(common.clone(), rel1.clone()).unwrap(), map1.clone(), Line ); assert_equiv_maps!( - BinaryComposition::new(rel2.clone(), common.clone()).unwrap(), + BinaryComposition::new(common.clone(), rel2.clone()).unwrap(), map2.clone(), Line ); From ecaee7e6ae7714b300ff909b6e61ee393723521e Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Sat, 2 Jul 2022 23:23:42 +0200 Subject: [PATCH 30/45] WIP --- src/elementary.rs | 95 ++++++--- src/lib.rs | 23 ++ src/ops.rs | 75 ++++++- src/relative.rs | 512 +++++++++++++++++++++++++-------------------- src/tesselation.rs | 87 +++++++- src/topology.rs | 2 +- 6 files changed, 533 insertions(+), 261 deletions(-) diff --git a/src/elementary.rs b/src/elementary.rs index 77e340d2a..f7dead306 100644 --- a/src/elementary.rs +++ b/src/elementary.rs @@ -5,21 +5,22 @@ use num::Integer as _; use std::ops::{Deref, DerefMut}; use std::rc::Rc; +/// An interface for an unbounded coordinate and index map. pub trait UnboundedMap { - // Minimum dimension of the input coordinate. If the dimension of the input - // coordinate of [UnboundedMap::apply_inplace()] is larger than the minimum, then - // the map of the surplus is the identity map. + /// Minimum dimension of the input coordinate. If the dimension of the input + /// coordinate of [`UnboundedMap::apply_inplace()`] is larger than the minimum, then + /// the map of the surplus is the identity map. fn dim_in(&self) -> usize; - // Minimum dimension of the output coordinate. + /// Minimum dimension of the output coordinate. fn dim_out(&self) -> usize { self.dim_in() + self.delta_dim() } - // Difference in dimension of the output and input coordinate. + /// Difference in dimension of the output and input coordinate. fn delta_dim(&self) -> usize; - // Modulus of the input index. The map repeats itself at index `mod_in` - // and the output index is incremented with `in_index / mod_in * mod_out`. + /// Modulus of the input index. The map repeats itself at index `mod_in` + /// and the output index is incremented with `in_index / mod_in * mod_out`. fn mod_in(&self) -> usize; - // Modulus if the output index. + /// Modulus if the output index. fn mod_out(&self) -> usize; fn apply_mod_out_to_in(&self, n: usize) -> Option { let (i, rem) = n.div_rem(&self.mod_out()); @@ -29,6 +30,7 @@ pub trait UnboundedMap { let (i, rem) = n.div_rem(&self.mod_in()); (rem == 0).then(|| i * self.mod_out()) } + /// Apply the given index and coordinate, the latter in-place. fn apply_inplace( &self, index: usize, @@ -36,16 +38,24 @@ pub trait UnboundedMap { stride: usize, offset: usize, ) -> usize; + /// Apply the index. fn apply_index(&self, index: usize) -> usize; + /// Apply a sequence of indices in-place. fn apply_indices_inplace(&self, indices: &mut [usize]) { for index in indices.iter_mut() { *index = self.apply_index(*index); } } + /// Unapply a sequence of indices. fn unapply_indices(&self, indices: &[T]) -> Vec; + /// Returns true if this is the identity map. fn is_identity(&self) -> bool { self.mod_in() == 1 && self.mod_out() == 1 && self.dim_out() == 0 } + /// Returns true if this map manipulates indices only. + fn is_index_map(&self) -> bool { + self.dim_out() == 0 + } } fn coords_iter_mut( @@ -518,6 +528,7 @@ impl UnboundedMap for UniformPoints { } } +/// An enum of elementary maps. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum Elementary { Transpose(Transpose), @@ -619,6 +630,7 @@ impl UnboundedMap for Elementary { dispatch! {fn apply_indices_inplace(&self, indices: &mut [usize])} dispatch! {fn unapply_indices(&self, indices: &[T]) -> Vec} dispatch! {fn is_identity(&self) -> bool} + dispatch! {fn is_index_map(&self) -> bool} } impl AddOffset for Elementary { @@ -842,6 +854,20 @@ impl Map for WithBounds { fn is_identity(&self) -> bool { self.map.is_identity() } + #[inline] + fn is_index_map(&self) -> bool { + self.map.is_index_map() + } +} + +impl AddOffset for WithBounds +where + M: UnboundedMap + AddOffset, +{ + fn add_offset(&mut self, offset: usize) { + self.dim_in += offset; + self.map.add_offset(offset); + } } /// An interface for swapping a composition of `Self` with an [`Elementary`]. @@ -992,7 +1018,11 @@ where } } -/// An interface for iterating over all possible decompositions into `Self` and an [`Elementary`]. +/// Return type of [`AllElementaryDecompositions::all_elementary_decompositions()`]. +pub type ElementaryDecompositionIter<'a, T> = + Box + 'a>; + +/// An interface for iterating over all possible decompositions into [`Elementary`] and `Self`. pub trait AllElementaryDecompositions: Sized { /// Return an iterator over all possible decompositions into `Self` and an [`Elementary`]. /// @@ -1016,15 +1046,11 @@ pub trait AllElementaryDecompositions: Sized { /// ); /// assert_eq!(iter.next(), None); /// ``` - fn all_elementary_decompositions<'a>( - &'a self, - ) -> Box + 'a>; + fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self>; } impl AllElementaryDecompositions for Vec { - fn all_elementary_decompositions<'a>( - &'a self, - ) -> Box + 'a> { + fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self> { let mut splits = Vec::new(); for (i, item) in self.iter().enumerate() { if let Some((outer, mut inner)) = (&self[..i]).swap_elementary_composition(item, 1) { @@ -1051,10 +1077,11 @@ impl AllElementaryDecompositions for Vec { } } -impl AllElementaryDecompositions for WithBounds { - fn all_elementary_decompositions<'a>( - &'a self, - ) -> Box + 'a> { +impl AllElementaryDecompositions for WithBounds +where + M: UnboundedMap + AllElementaryDecompositions, +{ + fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self> { Box::new( self.unbounded() .all_elementary_decompositions() @@ -1069,6 +1096,22 @@ impl AllElementaryDecompositions } } +/// Create a bounded composition of elementary maps. +/// +/// # Syntax +/// +/// The arguments of the macro are separated by `<-`, indicating the direction +/// of the map. The first argument is a simplex (`Triangle`, `Line`) or +/// `Point`, multiplied with the output length of the map. The remaining arguments +/// are elementary maps: `Children`, `Edges`, `Transpose(len1, len2)` or `Take(indices, len)`. +/// +/// # Examples +/// +/// ``` +/// use nutils_test::elementaries; +/// use nutils_test::simplex::Simplex::*; +/// elementaries![Line*2 <- Children <- Edges]; +/// ``` #[macro_export] macro_rules! elementaries { (Point*$len_out:literal $($tail:tt)*) => {{ @@ -1083,10 +1126,14 @@ macro_rules! elementaries { #[allow(unused_mut)] let mut comp: Vec = Vec::new(); $crate::elementaries!{@adv comp, $simplex; $($tail)*} - WithBounds::from_output(comp, $simplex.dim(), $len_out).unwrap() + let dim_out = $crate::elementaries!(@dim $simplex); + WithBounds::from_output(comp, dim_out, $len_out).unwrap() }}; - (@adv $comp:ident, $simplex:ident;) => {}; - (@adv $comp:ident, $simplex:ident; <- Children $($tail:tt)*) => {{ + (@dim Point) => {0}; + (@dim Line) => {1}; + (@dim Triangle) => {2}; + (@adv $comp:ident, $simplex:tt;) => {}; + (@adv $comp:ident, $simplex:tt; <- Children $($tail:tt)*) => {{ $comp.push(Elementary::new_children($crate::simplex::Simplex::$simplex)); $crate::elementaries!{@adv $comp, $simplex; $($tail)*} }}; @@ -1098,11 +1145,11 @@ macro_rules! elementaries { $comp.push(Elementary::new_edges($crate::simplex::Simplex::Line)); $crate::elementaries!{@adv $comp, Point; $($tail)*} }}; - (@adv $comp:ident, $simplex:ident; <- Transpose($len1:expr, $len2:expr) $($tail:tt)*) => {{ + (@adv $comp:ident, $simplex:tt; <- Transpose($len1:expr, $len2:expr) $($tail:tt)*) => {{ $comp.push(Elementary::new_transpose($len1, $len2)); $crate::elementaries!{@adv $comp, $simplex; $($tail)*} }}; - (@adv $comp:ident, $simplex:ident; <- Take($indices:expr, $len:expr) $($tail:tt)*) => {{ + (@adv $comp:ident, $simplex:tt; <- Take($indices:expr, $len:expr) $($tail:tt)*) => {{ $comp.push(Elementary::new_take($indices.to_vec(), $len)); $crate::elementaries!{@adv $comp, $simplex; $($tail)*} }}; diff --git a/src/lib.rs b/src/lib.rs index fc264181a..8008823a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,14 +26,23 @@ impl std::fmt::Display for Error { } } +/// An interface for an index and coordinate map. pub trait Map { + /// Returns the exclusive upper bound of the indices in the codomain. fn len_out(&self) -> usize; + /// Returns the exclusive upper bound of the indices of the domain. fn len_in(&self) -> usize; + /// Returns the dimension of the coordinates of the codimain. fn dim_out(&self) -> usize { self.dim_in() + self.delta_dim() } + /// Returns the dimension of the coordinates of the dimain. fn dim_in(&self) -> usize; + /// Returns the dimension difference of the coordinates in the codomain and the domain. fn delta_dim(&self) -> usize; + /// Apply the given index and coordinate, the latter in-place, without + /// checking whether the index is inside the domain and the coordinates + /// have at least dimension [`Self::dim_out()`]. fn apply_inplace_unchecked( &self, index: usize, @@ -41,6 +50,9 @@ pub trait Map { stride: usize, offset: usize, ) -> usize; + /// Applies the given index and coordinate, the latter in-place. The + /// coordinates must have a dimension not smaller than + /// [`Self::dim_out()`]. Returns `None` if the index is outside the domain. fn apply_inplace( &self, index: usize, @@ -54,7 +66,11 @@ pub trait Map { None } } + /// Applies the given index without checking that the index is inside the + /// domain. fn apply_index_unchecked(&self, index: usize) -> usize; + /// Apply the given index. Returns `None` if the index is outside the + /// domain, fn apply_index(&self, index: usize) -> Option { if index < self.len_in() { Some(self.apply_index_unchecked(index)) @@ -62,11 +78,15 @@ pub trait Map { None } } + /// Applies the given indices in-place without checking that the indices are + /// inside the domain. fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { for index in indices.iter_mut() { *index = self.apply_index_unchecked(*index); } } + /// Applies the given indices in-place. Returns `None` if any of the + /// indices is outside the domain. fn apply_indices(&self, indices: &[usize]) -> Option> { if indices.iter().all(|index| *index < self.len_in()) { let mut indices = indices.to_vec(); @@ -84,7 +104,10 @@ pub trait Map { None } } + /// Returns true if this map is the identity map. fn is_identity(&self) -> bool; + /// Returns true if this map returns coordinates unaltered. + fn is_index_map(&self) -> bool; } pub trait AddOffset { diff --git a/src/ops.rs b/src/ops.rs index fe8e3469c..50d2dc5c3 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -2,10 +2,17 @@ use crate::{Error, Map, UnapplyIndicesData}; use num::Integer as _; use std::ops::Deref; +/// The composition of two maps. #[derive(Debug, Clone, PartialEq)] pub struct BinaryComposition(Outer, Inner); impl BinaryComposition { + /// Returns the composition of two maps. + /// + /// The input dimension and length of the first map must equal the output + /// dimension and length of the second map. + /// + /// Returns an [`Error`] if the dimensions and lengths don't match. pub fn new(outer: Outer, inner: Inner) -> Result { if inner.dim_out() != outer.dim_in() { Err(Error::DimensionMismatch) @@ -15,9 +22,11 @@ impl BinaryComposition { Ok(Self(outer, inner)) } } + /// Returns the outer map of the composition. pub fn outer(&self) -> &Outer { &self.0 } + /// Returns the inner map of the composition. pub fn inner(&self) -> &Inner { &self.1 } @@ -73,10 +82,15 @@ impl Map for BinaryComposition { fn is_identity(&self) -> bool { self.0.is_identity() && self.1.is_identity() } + #[inline] + fn is_index_map(&self) -> bool { + self.0.is_index_map() && self.1.is_index_map() + } } +/// The composition of an unempty sequence of maps. #[derive(Debug, Clone, PartialEq)] -pub struct UniformComposition(Array) +pub struct UniformComposition>(Array) where M: Map, Array: Deref; @@ -86,6 +100,14 @@ where M: Map, Array: Deref, { + /// Returns the composition of an unempty sequence of maps. + /// + /// For every consecutive pair of maps in the sequence the input dimension + /// and length of the first map must equal the output dimension and length + /// of the second map. + /// + /// Returns an [`Error`] if the dimensions and lengths don't match or the + /// sequence is empty. pub fn new(array: Array) -> Result { let mut iter = array.iter().rev(); if let Some(map) = iter.next() { @@ -174,12 +196,23 @@ where fn is_identity(&self) -> bool { self.iter().all(|map| map.is_identity()) } + #[inline] + fn is_index_map(&self) -> bool { + self.iter().all(|map| map.is_index_map()) + } } +/// The concatenation of two maps. #[derive(Debug, Clone, PartialEq)] pub struct BinaryConcat(M0, M1); impl BinaryConcat { + /// Returns the concatenation of two maps. + /// + /// The two maps must have the same input and output dimensions and the + /// same output length. The maps must not overlap. + /// + /// Returns an [`Error`] if the dimensions and lengths don't match. pub fn new(map0: M0, map1: M1) -> Result { if map0.dim_in() != map1.dim_in() || map0.dim_out() != map1.dim_out() { Err(Error::DimensionMismatch) @@ -192,6 +225,14 @@ impl BinaryConcat { pub fn new_unchecked(map0: M0, map1: M1) -> Self { Self(map0, map1) } + /// Returns the first map of the concatenation. + pub fn first(&self) -> &M0 { + &self.0 + } + /// Returns the second map of the concatenation. + pub fn second(&self) -> &M1 { + &self.1 + } } impl Map for BinaryConcat { @@ -254,10 +295,15 @@ impl Map for BinaryConcat { fn is_identity(&self) -> bool { false } + #[inline] + fn is_index_map(&self) -> bool { + self.0.is_index_map() && self.1.is_index_map() + } } +/// The concatenation of an unempty sequence of maps. #[derive(Debug, Clone, PartialEq)] -pub struct UniformConcat(Array) +pub struct UniformConcat>(Array) where M: Map, Array: Deref; @@ -267,6 +313,12 @@ where M: Map, Array: Deref, { + /// Returns the concatenation of an unempty sequence of maps. + /// + /// The two maps must have the same input and output dimensions and the + /// same output length. The maps must not overlap. + /// + /// Returns an [`Error`] if the dimensions and lengths don't match. pub fn new(array: Array) -> Result { let mut iter = array.iter(); if let Some(map) = iter.next() { @@ -364,18 +416,26 @@ where fn is_identity(&self) -> bool { false } + #[inline] + fn is_index_map(&self) -> bool { + self.iter().all(|item| item.is_index_map()) + } } +/// The product of two maps. #[derive(Debug, Clone, PartialEq)] pub struct BinaryProduct(M0, M1); impl BinaryProduct { + /// Returns the product of two maps. pub fn new(map0: M0, map1: M1) -> Self { Self(map0, map1) } + /// Returns the first term of the product. pub fn first(&self) -> &M0 { &self.0 } + /// Returns the second term of the product. pub fn second(&self) -> &M1 { &self.1 } @@ -451,6 +511,10 @@ impl Map for BinaryProduct { fn is_identity(&self) -> bool { self.0.is_identity() && self.1.is_identity() } + #[inline] + fn is_index_map(&self) -> bool { + self.0.is_index_map() && self.1.is_index_map() + } } #[derive(Debug, Clone)] @@ -465,8 +529,9 @@ impl UnapplyIndicesData for UnapplyBinaryProduct { } } +/// The product of a sequence of maps. #[derive(Debug, Clone, PartialEq)] -pub struct UniformProduct(Array) +pub struct UniformProduct>(Array) where M: Map, Array: Deref; @@ -476,9 +541,11 @@ where M: Map, Array: Deref, { + /// Returns the product of a sequence of maps. pub fn new(array: Array) -> Self { Self(array) } + /// Returns an iterator of the terms of the product. pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, M> { self.0.iter() } @@ -646,6 +713,7 @@ where dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} dispatch! {fn is_identity(&self) -> bool} + dispatch! {fn is_index_map(&self) -> bool} } impl FromIterator for UniformProduct> { @@ -664,7 +732,6 @@ mod tests { use super::*; use crate::assert_map_apply; use crate::elementaries; - use crate::simplex::Simplex::*; use approx::assert_abs_diff_eq; use std::iter; diff --git a/src/relative.rs b/src/relative.rs index e6d92d496..ced43bf4c 100644 --- a/src/relative.rs +++ b/src/relative.rs @@ -1,18 +1,19 @@ use crate::elementary::{ - AllElementaryDecompositions, Elementary, SwapElementaryComposition, UnboundedMap, WithBounds, + AllElementaryDecompositions, Elementary, ElementaryDecompositionIter, + SwapElementaryComposition, UnboundedMap, WithBounds, Identity, Slice, }; -use crate::ops::{BinaryComposition, BinaryProduct, UniformProduct}; +use crate::ops::{BinaryComposition, BinaryProduct, UniformProduct, UniformConcat}; use crate::util::ReplaceNthIter as _; -use crate::{AddOffset, Map}; +use crate::{AddOffset, Map, UnapplyIndicesData}; use std::collections::BTreeMap; +use std::rc::Rc; +use std::iter; impl AllElementaryDecompositions for UniformProduct> where M: Map + AllElementaryDecompositions + Clone, { - fn all_elementary_decompositions<'a>( - &'a self, - ) -> Box + 'a> { + fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self> { Box::new( self.iter() .enumerate() @@ -41,9 +42,7 @@ where M0: Map + AllElementaryDecompositions + Clone, M1: Map + AllElementaryDecompositions + Clone, { - fn all_elementary_decompositions<'a>( - &'a self, - ) -> Box + 'a> { + fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self> { let first = self .first() .all_elementary_decompositions() @@ -94,10 +93,32 @@ where } } -/// Decompose two maps into two remainders and a common map. +/// Decompose two maps into a common map and two remainders. /// -/// The decomposition is such that the composition the common part with the +/// The decomposition is such that the composition of the common part with the /// remainder gives a map that is equivalent to the original. +/// +/// # Examples +/// +/// ``` +/// use nutils_test::elementaries; +/// let map1 = elementaries![Line*1 <- Children <- Children <- Edges]; +/// let map2 = elementaries![Line*1 <- Children <- Edges]; +/// let (common, rem1, rem2) = nutils_test::relative::decompose_common(map1, map2); +/// assert_eq!(common, elementaries![Line*1 <- Children <- Children <- Edges]); +/// assert_eq!(rem1, elementaries![Point*8]); +/// assert_eq!(rem2, elementaries![Point*8 <- Take([2, 1], 4)]); +/// ``` +/// +/// ``` +/// use nutils_test::elementaries; +/// let map1 = elementaries![Triangle*1 <- Children]; +/// let map2 = elementaries![Triangle*1 <- Edges <- Children]; +/// let (common, rem1, rem2) = nutils_test::relative::decompose_common(map1, map2); +/// assert_eq!(common, elementaries![Triangle*1 <- Children]); +/// assert_eq!(rem1, elementaries![Triangle*4]); +/// assert_eq!(rem2, elementaries![Triangle*4 <- Edges <- Take([3, 6, 1, 7, 2, 5], 12)]); +/// ``` pub fn decompose_common(mut map1: M1, mut map2: M2) -> (WithBounds>, M1, M2) where M1: Map + AllElementaryDecompositions, @@ -122,199 +143,230 @@ where (map1, map2) } else { break; - } + }; + assert_eq!(map1.len_out(), map2.len_out()); + assert_eq!(map1.dim_out(), map2.dim_out()); } - common.reverse(); let common = WithBounds::from_input(common, map1.dim_out(), map1.len_out()).unwrap(); (common, map1, map2) } -//#[derive(Debug, Clone, PartialEq)] -//pub enum Relative { -// Identity(WithBounds), -// Single(WithBounds>), -// Multiple(RelativeMultiple), -// Concatenation(Concatenation), -//} -// -//macro_rules! dispatch { -// ( -// $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( -// &$self:ident $(, $arg:ident: $ty:ty)* -// ) $(-> $ret:ty)? -// ) => { -// #[inline] -// $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { -// dispatch!(@match $self; $fn; $($arg),*) -// } -// }; -// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { -// #[inline] -// $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { -// dispatch!(@match $self; $fn; $($arg),*) -// } -// }; -// (@match $self:ident; $fn:ident; $($arg:ident),*) => { -// match $self { -// Relative::Identity(var) => var.$fn($($arg),*), -// Relative::Single(var) => var.$fn($($arg),*), -// Relative::Multiple(var) => var.$fn($($arg),*), -// Relative::Concatenation(var) => var.$fn($($arg),*), -// } -// } -//} -// -//impl BoundedMap for Relative { -// dispatch! {fn len_out(&self) -> usize} -// dispatch! {fn len_in(&self) -> usize} -// dispatch! {fn dim_out(&self) -> usize} -// dispatch! {fn dim_in(&self) -> usize} -// dispatch! {fn delta_dim(&self) -> usize} -// dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} -// dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} -// dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} -// dispatch! {fn apply_index(&self, index: usize) -> Option} -// dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} -// dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} -// dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} -// dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} -// dispatch! {fn is_identity(&self) -> bool} -//} -// +fn partial_relative_to(source: Source, target: Target) -> PartialRelative +where + Source: Map + AllElementaryDecompositions, + Target: Map + AllElementaryDecompositions, +{ + let (_, rem, rel) = decompose_common(target, source); + if rem.is_identity() { + PartialRelative::AllSameOrder(rel) + } else if rem.is_index_map() { + // TODO: transfer transposes from `rem` to `rel` if this would make `rem` the identity + let mut indices: Vec = (0..rem.len_in()).collect(); + rem.apply_indices_inplace_unchecked(&mut indices); + PartialRelative::Some(rel, indices) + } else { + PartialRelative::CannotEstablishRelation + } +} + +enum PartialRelative { + AllSameOrder(M), + Some(M, Vec), + CannotEstablishRelation, +} + +/// An interface for determining the relation between two maps. +pub trait RelativeTo { + /// Return the relative map from `self` to the given target. + fn relative_to(&self, target: &Target) -> Option; + /// Map indices for the target to `self`. + fn unapply_indices_from(&self, target: &Target, indices: &[T]) -> Option> + where + T: UnapplyIndicesData, + { + self.relative_to(target) + .and_then(|rel| rel.unapply_indices(indices)) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Relative { + Identity(WithBounds), + Slice(WithBounds), + Elementaries(WithBounds>), + Concat(UniformConcat), + RelativeToConcat(RelativeToConcat), +} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $(-> $ret:ty)? + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { + dispatch!(@match $self; $fn; $($arg),*) + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { + dispatch!(@match $self; $fn; $($arg),*) + } + }; + (@match $self:ident; $fn:ident; $($arg:ident),*) => { + match $self { + Relative::Identity(var) => var.$fn($($arg),*), + Relative::Slice(var) => var.$fn($($arg),*), + Relative::Elementaries(var) => var.$fn($($arg),*), + Relative::Concat(var) => var.$fn($($arg),*), + Relative::RelativeToConcat(var) => var.$fn($($arg),*), + } + } +} + +impl Map for Relative { + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} + dispatch! {fn is_index_map(&self) -> bool} +} + //impl AddOffset for Relative { // dispatch! {fn add_offset(&mut self, offset: usize)} //} -// -//pub trait RelativeTo { -// fn relative_to(&self, target: &Target) -> Option; -// fn unapply_indices_from( -// &self, -// target: &Target, -// indices: &[T], -// ) -> Option> { -// self.relative_to(target) -// .and_then(|rel| rel.unapply_indices(indices)) -// } -//} -// -//impl RelativeTo for WithBounds> { -// fn relative_to(&self, target: &Self) -> Option { -// let (_, rem, rel) = target -// .get_unbounded() -// .split_common_prefix_opt_lhs(self.get_unbounded()); -// rem.is_identity() -// .then(|| Relative::Single(Self::new_unchecked(rel, target.dim_in(), target.len_in()))) -// } -//} -// -//impl RelativeTo for Concatenation -//where -// Item: BoundedMap + RelativeTo, -// Target: BoundedMap, -//{ -// fn relative_to(&self, target: &Target) -> Option { -// self.iter() -// .map(|item| item.relative_to(target)) -// .collect::>() -// .map(|rel_items| Relative::Concatenation(Concatenation::new(rel_items))) -// } -//} -// -//fn pop_common(vecs: &mut [&mut Vec]) -> Option { -// let item = vecs.first().and_then(|vec| vec.last()); -// if item.is_some() && vecs[1..].iter().all(|vec| vec.last() == item) { -// for vec in vecs[1..].iter_mut() { -// vec.pop(); -// } -// vecs[0].pop() -// } else { -// None -// } -//} -// -//#[derive(Debug, Clone)] -//struct IndexOutIn(usize, usize); -// -//impl UnapplyIndicesData for IndexOutIn { -// #[inline] -// fn last(&self) -> usize { -// self.1 -// } -// #[inline] -// fn push(&self, index: usize) -> Self { -// Self(self.0, index) -// } -//} -// -//#[derive(Debug, Clone, PartialEq)] -//pub struct RelativeMultiple { -// rels: Vec>, -// index_map: Rc>, -// common: Vec, -// len_out: usize, -// len_in: usize, -// dim_in: usize, -// delta_dim: usize, -//} -// -//impl BoundedMap for RelativeMultiple { -// fn dim_in(&self) -> usize { -// self.dim_in -// } -// fn delta_dim(&self) -> usize { -// self.delta_dim -// } -// fn len_in(&self) -> usize { -// self.len_in -// } -// fn len_out(&self) -> usize { -// self.len_out -// } -// fn apply_inplace_unchecked( -// &self, -// index: usize, -// coordinates: &mut [f64], -// stride: usize, -// offset: usize, -// ) -> usize { -// let index = self -// .common -// .apply_inplace(index, coordinates, stride, offset); -// let (iout, iin) = self.index_map[index]; -// let n = self.index_map.len(); -// self.rels[iin / n].apply_inplace(iin % n, coordinates, stride, offset); -// iout -// } -// fn apply_index_unchecked(&self, index: usize) -> usize { -// self.index_map[self.common.apply_index(index)].0 -// } -// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { -// self.common.apply_indices_inplace(indices); -// for index in indices.iter_mut() { -// *index = self.index_map[*index].0; -// } -// } -// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { -// // FIXME: VERY EXPENSIVE!!! -// let mut in_indices: Vec = Vec::new(); -// for index in indices { -// in_indices.extend( -// self.index_map -// .iter() -// .enumerate() -// .filter_map(|(iin, (iout, _))| { -// (*iout == index.last()).then(|| index.push(iin)) -// }), -// ); -// } -// self.common.unapply_indices(&in_indices) -// } -// fn is_identity(&self) -> bool { -// false -// } -//} -// -//impl AddOffset for RelativeMultiple { + +impl RelativeTo for WithBounds> { + fn relative_to(&self, target: &Self) -> Option { + let (_, rem, rel) = decompose_common(target.clone(), self.clone()); + // TODO: transfer transposes from `rem` to `rel` + rem.is_identity().then(|| Relative::Elementaries(rel)) + } +} + +impl RelativeTo for UniformConcat +where + Source: Map + RelativeTo, + Target: Map, +{ + fn relative_to(&self, target: &Target) -> Option { + self.iter() + .map(|item| item.relative_to(target)) + .collect::>() + .map(|rels| Relative::Concat(UniformConcat::new_unchecked(rels))) + } +} + +fn pop_common(vecs: &mut [&mut Vec]) -> Option { + let item = vecs.first().and_then(|vec| vec.last()); + if item.is_some() && vecs[1..].iter().all(|vec| vec.last() == item) { + for vec in vecs[1..].iter_mut() { + vec.pop(); + } + vecs[0].pop() + } else { + None + } +} + +#[derive(Debug, Clone)] +struct IndexOutIn(usize, usize); + +impl UnapplyIndicesData for IndexOutIn { + #[inline] + fn get(&self) -> usize { + self.1 + } + #[inline] + fn set(&self, index: usize) -> Self { + Self(self.0, index) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct RelativeToConcat { + rels: Vec>, + index_map: Rc>, + common: Vec, + len_out: usize, + len_in: usize, + dim_in: usize, + delta_dim: usize, +} + +impl Map for RelativeToConcat { + fn dim_in(&self) -> usize { + self.dim_in + } + fn delta_dim(&self) -> usize { + self.delta_dim + } + fn len_in(&self) -> usize { + self.len_in + } + fn len_out(&self) -> usize { + self.len_out + } + fn apply_inplace_unchecked( + &self, + index: usize, + coordinates: &mut [f64], + stride: usize, + offset: usize, + ) -> usize { + let index = self + .common + .apply_inplace(index, coordinates, stride, offset); + let (iout, iin) = self.index_map[index]; + let n = self.index_map.len(); + self.rels[iin / n].apply_inplace(iin % n, coordinates, stride, offset); + iout + } + fn apply_index_unchecked(&self, index: usize) -> usize { + self.index_map[self.common.apply_index(index)].0 + } + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + self.common.apply_indices_inplace(indices); + for index in indices.iter_mut() { + *index = self.index_map[*index].0; + } + } + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { + // FIXME: VERY EXPENSIVE!!! + let mut in_indices: Vec = Vec::new(); + for index in indices { + in_indices.extend( + self.index_map + .iter() + .enumerate() + .filter_map(|(iin, (iout, _))| { + (*iout == index.get()).then(|| index.set(iin)) + }), + ); + } + self.common.unapply_indices(&in_indices) + } + fn is_identity(&self) -> bool { + false + } + fn is_index_map(&self) -> bool { + false // TODO + } +} + +//impl AddOffset for RelativeToConcat { // fn add_offset(&mut self, offset: usize) { // self.common.add_offset(offset); // for rel in self.rels.iter_mut() { @@ -323,25 +375,29 @@ where // self.dim_in += offset; // } //} -// -//impl RelativeTo> for WithBounds> { -// fn relative_to(&self, targets: &Concatenation) -> Option { + +//impl RelativeTo> for Source +//where +// Source: Map + AllElementaryDecompositions + Clone, +// Target: Map + AllElementaryDecompositions + Clone, +//{ +// fn relative_to(&self, targets: &UniformConcat) -> Option { // let mut rels_indices = Vec::new(); // let mut offset = 0; -// for target in targets.iter() { -// let (_, rem, rel) = target -// .get_unbounded() -// .split_common_prefix_opt_lhs(self.get_unbounded()); -// if rem.is_identity() { -// let slice = Elementary::new_slice(offset, target.len_in(), targets.len_in()); -// let rel: Vec = iter::once(slice).chain(rel).collect(); -// let rel = WithBounds::new_unchecked(rel, targets.dim_in(), targets.len_in()); -// return Some(Relative::Single(rel)); -// } -// if rem.dim_out() == 0 { -// let mut indices: Vec = (0..target.len_in()).collect(); -// rem.apply_indices_inplace(&mut indices); -// rels_indices.push((rel, offset, indices)) +// for target in targets.iter().cloned() { +// match partial_relative_to(self.clone(), target) { +// PartialRelative::AllSameOrder(rel) => { +// let slice = Slice::new(offset, target.len_in(), targets.len_in()); +// let slice = WithBounds::from_input(slice, target.dim_in(), target.len_in()).unwrap(); +// let slice = Relative::Slice(slice); +// return Some(Relative::Concat(UniformConcat::new_unchecked(vec![slice, rel]))); +// } +// PartialRelative::Some(rel, indices) => { +// rels_indices.push((rel, offset, indices)) +// } +// PartialRelative::CannotEstablishRelation => { +// return None; +// } // } // offset += target.len_in(); // } @@ -379,7 +435,7 @@ where // .into_iter() // .collect::>>() // .map(|index_map| { -// Relative::Multiple(RelativeMultiple { +// Relative::RelativeToConcat(RelativeToConcat { // index_map: index_map.into(), // rels, // common, @@ -391,7 +447,7 @@ where // }) // } //} -// + #[cfg(test)] mod tests { use super::*; @@ -469,17 +525,17 @@ mod tests { //assert_eq!(common, elementaries![Line*2]); // WithBounds { map: [Offset(Children(Line), 1), Transpose(2, 1), Offset(Children(Line), 0)], dim_in: 2, delta_dim: 0, len_in: 16, len_out: 4 } } - // - // #[test] - // fn rel_to() { - // let a1 = elementaries![Line*2 <- Children <- Take([0, 2], 4)]; - // let a2 = elementaries![Line*2 <- Children <- Take([1, 3], 4) <- Children]; - // let a = BinaryConcat::new(a1, a2).unwrap(); - // let b = elementaries![Line*2 <- Children <- Children <- Children]; - // assert_equiv_maps!( - // BinaryComposition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), - // b, - // Line - // ); - // } + +// #[test] +// fn rel_to_single() { +// let a1 = elementaries![Line*2 <- Children <- Take([0, 2], 4)]; +// let a2 = elementaries![Line*2 <- Children <- Take([1, 3], 4) <- Children]; +// let a = BinaryConcat::new(a1, a2).unwrap(); +// let b = elementaries![Line*2 <- Children <- Children <- Children]; +// assert_equiv_maps!( +// BinaryComposition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), +// b, +// Line +// ); +// } } diff --git a/src/tesselation.rs b/src/tesselation.rs index f37964d9b..a43c2be89 100644 --- a/src/tesselation.rs +++ b/src/tesselation.rs @@ -8,6 +8,43 @@ struct UniformTesselation { map: WithBounds>, } +impl UniformTesselation { + fn identity(shapes: Vec, len: usize) -> Self { + let dim = shapes.iter().map(|simplex| simplex.dim()).sum(); + let map = WithBounds::from_output(vec![], dim, len); + Self { shapes, map } + } + fn children(&self) -> Self { + let mut map = self.map.clone(); + let mut offset = 0; + for shape in &self.shapes { + let item = Elementary::new_children(shape); + item.add_offset(offset); + map.push(item); + offset += shape.dim(); + } + Self { shapes: self.shapes.clone(), map } + } + fn edges(&self) -> Result { + if self.shapes().is_empty() { + return Err("dimension zero"); + } + let mut map = self.map.clone(); + let mut shapes = Vec::with_capacity(self.shapes.len()); + let mut offset = 0; + for shape in &self.shapes { + let item = Elementary::new_edges(shape); + item.add_offset(offset); + map.push(item); + offset += shape.edge_dim(); + if let Some(edge_shape) = shape.edge_shape() { + shapes.push(edge_shape); + } + } + Ok(Self { shapes, map }) + } +} + impl Deref for UniformTesselation { type Target = WithBounds>; @@ -16,13 +53,55 @@ impl Deref for UniformTesselation { } } -struct Tesselation { - maps: UniformConcat, - reorder: Option>, +pub struct Tesselation(UniformConcat, Vec>); + +impl Tesselation { + pub fn identity(shapes: Vec, len: usize) -> Self { + Self(UniformConcat::new_unchecked(vec![UniformTesselation::identity(shapes, len)])) + } + pub fn len(&self) -> Self { + self.0.len_in() + } + pub fn dim(&self) -> Self { + self.0.dim_in() + } + pub fn product(&self, other: &Self) -> Self { + unimplemented!{} + } + pub fn concatenate(&self, other: &Self) -> Self { + unimplemented!{} + } + pub fn take(&self, indices: &[usize]) -> Self { + unimplemented!{} + } + pub fn children(&self) -> Self { + Self(UniformConcat::new_unchecked(self.0.iter().map(|item| item.children()).collect())) + } + pub fn edges(&self) -> Result { + let edges = self.0.iter().map(|item| item.edges()).collect::>()?; + Self(UniformConcat::new_unchecked(edges)) + } + pub fn centroids(&self) -> Self { + unimplemented!{} + } + pub fn vertices(&self) -> Self { + unimplemented!{} + } + pub fn apply_inplace(&self, mut index: usize, coords: &mut [f64], stride: usize) -> Option { + self.0.apply_inplace(index, coords, stride, 0) + } + pub fn unapply_indices(&self, indices: &[T]) -> Vec { + self.0.unapply_indices(indices) + } } +impl Deref for Tesselation { + type Target = OptionReorder>, Vec>; -type Tesselation = OptionReorder>, Vec>; + fn deref(&self) -> Self::Target { + self.0 + } +} diff --git a/src/topology.rs b/src/topology.rs index b49b10f8e..a2c9a168c 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -7,7 +7,7 @@ use std::iter; use std::ops::{Deref, DerefMut}; use std::rc::Rc; -type Tesselation = Concatenation>>; +type Tesselation = UniformConcat>>; #[derive(Debug, Clone, PartialEq)] pub struct Root { From a674920fb294fc994eb7acc8fa62d2c168aea67e Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Sun, 3 Jul 2022 21:47:04 +0200 Subject: [PATCH 31/45] WIP --- src/relative.rs | 206 +++++++++++++++++++++++++----------------------- 1 file changed, 109 insertions(+), 97 deletions(-) diff --git a/src/relative.rs b/src/relative.rs index ced43bf4c..4d74931e0 100644 --- a/src/relative.rs +++ b/src/relative.rs @@ -176,9 +176,11 @@ enum PartialRelative { } /// An interface for determining the relation between two maps. -pub trait RelativeTo { +pub trait RelativeTo: Map + Sized { + type Output: Map; + /// Return the relative map from `self` to the given target. - fn relative_to(&self, target: &Target) -> Option; + fn relative_to(&self, target: &Target) -> Option; /// Map indices for the target to `self`. fn unapply_indices_from(&self, target: &Target, indices: &[T]) -> Option> where @@ -190,12 +192,12 @@ pub trait RelativeTo { } #[derive(Debug, Clone, PartialEq)] -pub enum Relative { +pub enum Relative { Identity(WithBounds), Slice(WithBounds), - Elementaries(WithBounds>), - Concat(UniformConcat), - RelativeToConcat(RelativeToConcat), + Map(M), + Concat(UniformConcat), + RelativeToConcat(RelativeToConcat), } macro_rules! dispatch { @@ -217,16 +219,16 @@ macro_rules! dispatch { }; (@match $self:ident; $fn:ident; $($arg:ident),*) => { match $self { - Relative::Identity(var) => var.$fn($($arg),*), - Relative::Slice(var) => var.$fn($($arg),*), - Relative::Elementaries(var) => var.$fn($($arg),*), - Relative::Concat(var) => var.$fn($($arg),*), - Relative::RelativeToConcat(var) => var.$fn($($arg),*), + Self::Identity(var) => var.$fn($($arg),*), + Self::Slice(var) => var.$fn($($arg),*), + Self::Map(var) => var.$fn($($arg),*), + Self::Concat(var) => var.$fn($($arg),*), + Self::RelativeToConcat(var) => var.$fn($($arg),*), } } } -impl Map for Relative { +impl Map for Relative { dispatch! {fn len_out(&self) -> usize} dispatch! {fn len_in(&self) -> usize} dispatch! {fn dim_out(&self) -> usize} @@ -249,10 +251,12 @@ impl Map for Relative { //} impl RelativeTo for WithBounds> { - fn relative_to(&self, target: &Self) -> Option { + type Output = Self; + + fn relative_to(&self, target: &Self) -> Option { let (_, rem, rel) = decompose_common(target.clone(), self.clone()); // TODO: transfer transposes from `rem` to `rel` - rem.is_identity().then(|| Relative::Elementaries(rel)) + rem.is_identity().then(|| rel) } } @@ -261,11 +265,13 @@ where Source: Map + RelativeTo, Target: Map, { - fn relative_to(&self, target: &Target) -> Option { + type Output = UniformConcat; + + fn relative_to(&self, target: &Target) -> Option { self.iter() .map(|item| item.relative_to(target)) .collect::>() - .map(|rels| Relative::Concat(UniformConcat::new_unchecked(rels))) + .map(|rels| UniformConcat::new_unchecked(rels)) } } @@ -296,17 +302,17 @@ impl UnapplyIndicesData for IndexOutIn { } #[derive(Debug, Clone, PartialEq)] -pub struct RelativeToConcat { - rels: Vec>, +pub struct RelativeToConcat { + rels: Vec, index_map: Rc>, - common: Vec, + //common: Vec, len_out: usize, len_in: usize, dim_in: usize, delta_dim: usize, } -impl Map for RelativeToConcat { +impl Map for RelativeToConcat { fn dim_in(&self) -> usize { self.dim_in } @@ -326,19 +332,20 @@ impl Map for RelativeToConcat { stride: usize, offset: usize, ) -> usize { - let index = self - .common - .apply_inplace(index, coordinates, stride, offset); + //let index = self + // .common + // .apply_inplace(index, coordinates, stride, offset); let (iout, iin) = self.index_map[index]; let n = self.index_map.len(); self.rels[iin / n].apply_inplace(iin % n, coordinates, stride, offset); iout } fn apply_index_unchecked(&self, index: usize) -> usize { - self.index_map[self.common.apply_index(index)].0 + //self.index_map[self.common.apply_index(index)].0 + self.index_map[index].0 } fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - self.common.apply_indices_inplace(indices); + //self.common.apply_indices_inplace(indices); for index in indices.iter_mut() { *index = self.index_map[*index].0; } @@ -356,7 +363,8 @@ impl Map for RelativeToConcat { }), ); } - self.common.unapply_indices(&in_indices) + //self.common.unapply_indices(&in_indices) + in_indices } fn is_identity(&self) -> bool { false @@ -376,77 +384,81 @@ impl Map for RelativeToConcat { // } //} -//impl RelativeTo> for Source -//where -// Source: Map + AllElementaryDecompositions + Clone, -// Target: Map + AllElementaryDecompositions + Clone, -//{ -// fn relative_to(&self, targets: &UniformConcat) -> Option { -// let mut rels_indices = Vec::new(); -// let mut offset = 0; -// for target in targets.iter().cloned() { -// match partial_relative_to(self.clone(), target) { -// PartialRelative::AllSameOrder(rel) => { -// let slice = Slice::new(offset, target.len_in(), targets.len_in()); -// let slice = WithBounds::from_input(slice, target.dim_in(), target.len_in()).unwrap(); -// let slice = Relative::Slice(slice); -// return Some(Relative::Concat(UniformConcat::new_unchecked(vec![slice, rel]))); -// } -// PartialRelative::Some(rel, indices) => { -// rels_indices.push((rel, offset, indices)) -// } -// PartialRelative::CannotEstablishRelation => { -// return None; -// } -// } -// offset += target.len_in(); -// } -// // Split off common tail. TODO: Only shape increasing items, not take, slice (and transpose?). -// let common_len_out = self.len_in(); -// let common = Vec::new(); -// //let mut common_len_out = self.len_in(); -// //let mut common = Vec::new(); -// //{ -// // let mut rels: Vec<_> = rels_indices.iter_mut().map(|(rel, _, _)| rel).collect(); -// // while let Some(item) = pop_common(&mut rels[..]) { -// // common_len_out = common_len_out / item.mod_in() * item.mod_out(); -// // common.push(item); -// // } -// //} -// // Build index map. -// let mut index_map: Vec> = -// iter::repeat(None).take(common_len_out).collect(); -// let mut rels = Vec::new(); -// for (irel, (rel, offset, out_indices)) in rels_indices.into_iter().enumerate() { -// let rel_indices: Vec<_> = (offset..offset + out_indices.len()) -// .zip(out_indices) -// .map(|(i, j)| IndexOutIn(i, j)) -// .collect(); -// for IndexOutIn(iout, iin) in rel.unapply_indices(&rel_indices) { -// assert!( -// index_map[iin].is_none(), -// "target contains duplicate entries" -// ); -// index_map[iin] = Some((iout, iin + irel * common_len_out)); -// } -// rels.push(rel); -// } -// index_map -// .into_iter() -// .collect::>>() -// .map(|index_map| { -// Relative::RelativeToConcat(RelativeToConcat { -// index_map: index_map.into(), -// rels, -// common, -// delta_dim: targets.dim_in() - self.dim_in(), -// dim_in: self.dim_in(), -// len_out: targets.len_in(), -// len_in: self.len_in(), -// }) -// }) -// } -//} +impl RelativeTo> for Source +where + Source: Map + AllElementaryDecompositions + Clone, + Target: Map + AllElementaryDecompositions + Clone, +{ + type Output = Relative; + + fn relative_to(&self, targets: &UniformConcat) -> Option> { + let mut rels_indices = Vec::new(); + let mut offset = 0; + for target in targets.iter().cloned() { + let new_offset = offset + target.len_in(); + match partial_relative_to(self.clone(), target) { + PartialRelative::AllSameOrder(rel) => { + let slice = Slice::new(offset, rel.len_out(), targets.len_in()); + let slice = WithBounds::from_input(slice, rel.dim_out(), rel.len_out()).unwrap(); + let slice = Relative::Slice(slice); + let rel = Relative::Map(rel); + return Some(Relative::Concat(UniformConcat::new_unchecked(vec![slice, rel]))); + } + PartialRelative::Some(rel, indices) => { + rels_indices.push((rel, offset, indices)); + } + PartialRelative::CannotEstablishRelation => { + return None; + } + } + offset = new_offset; + } + // Split off common tail. TODO: Only shape increasing items, not take, slice (and transpose?). + let common_len_out = self.len_in(); + //let common = Vec::new(); + //let mut common_len_out = self.len_in(); + //let mut common = Vec::new(); + //{ + // let mut rels: Vec<_> = rels_indices.iter_mut().map(|(rel, _, _)| rel).collect(); + // while let Some(item) = pop_common(&mut rels[..]) { + // common_len_out = common_len_out / item.mod_in() * item.mod_out(); + // common.push(item); + // } + //} + // Build index map. + let mut index_map: Vec> = + iter::repeat(None).take(common_len_out).collect(); + let mut rels = Vec::new(); + for (irel, (rel, offset, out_indices)) in rels_indices.into_iter().enumerate() { + let rel_indices: Vec<_> = (offset..offset + out_indices.len()) + .zip(out_indices) + .map(|(i, j)| IndexOutIn(i, j)) + .collect(); + for IndexOutIn(iout, iin) in rel.unapply_indices_unchecked(&rel_indices) { + assert!( + index_map[iin].is_none(), + "target contains duplicate entries" + ); + index_map[iin] = Some((iout, iin + irel * common_len_out)); + } + rels.push(rel); + } + index_map + .into_iter() + .collect::>>() + .map(|index_map| { + Relative::RelativeToConcat(RelativeToConcat { + index_map: index_map.into(), + rels, + //common, + delta_dim: targets.dim_in() - self.dim_in(), + dim_in: self.dim_in(), + len_out: targets.len_in(), + len_in: self.len_in(), + }) + }) + } +} #[cfg(test)] mod tests { From 6aa60e1d57150b9d25b8e2c2feed1186fa1a57d7 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Sun, 3 Jul 2022 21:55:29 +0200 Subject: [PATCH 32/45] WIP --- src/lib.rs | 2 +- src/ops.rs | 24 +-- src/{elementary.rs => primitive.rs} | 262 ++++++++++++++-------------- src/relative.rs | 142 +++++++-------- 4 files changed, 215 insertions(+), 215 deletions(-) rename src/{elementary.rs => primitive.rs} (82%) diff --git a/src/lib.rs b/src/lib.rs index 8008823a2..1b6164bf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -pub mod elementary; +pub mod primitive; pub mod finite_f64; pub mod ops; pub mod relative; diff --git a/src/ops.rs b/src/ops.rs index 50d2dc5c3..f4396edc7 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -731,13 +731,13 @@ impl FromIterator for UniformProduct> { mod tests { use super::*; use crate::assert_map_apply; - use crate::elementaries; + use crate::prim_comp; use approx::assert_abs_diff_eq; use std::iter; #[test] fn uniform_composition1() { - let map = UniformComposition::new(vec![elementaries![Point * 1]]).unwrap(); + let map = UniformComposition::new(vec![prim_comp![Point * 1]]).unwrap(); assert_eq!(map.len_in(), 1); assert_eq!(map.len_out(), 1); assert_eq!(map.dim_in(), 0); @@ -749,8 +749,8 @@ mod tests { #[test] fn uniform_composition2() { let map = UniformComposition::new(vec![ - elementaries![Point*12 <- Transpose(4, 3)], - elementaries![Point*12 <- Take([1, 0], 3)], + prim_comp![Point*12 <- Transpose(4, 3)], + prim_comp![Point*12 <- Take([1, 0], 3)], ]) .unwrap(); assert_eq!(map.len_in(), 8); @@ -771,9 +771,9 @@ mod tests { #[test] fn uniform_composition3() { let map = UniformComposition::new(vec![ - elementaries![Line*1 <- Children], - elementaries![Line*2 <- Children], - elementaries![Line*4 <- Edges], + prim_comp![Line*1 <- Children], + prim_comp![Line*2 <- Children], + prim_comp![Line*4 <- Edges], ]) .unwrap(); assert_eq!(map.len_in(), 8); @@ -798,9 +798,9 @@ mod tests { #[test] fn uniform_concat() { let map = UniformConcat::new(vec![ - elementaries![Line*3 <- Take([0], 3)], - elementaries![Line*3 <- Take([1], 3) <- Children], - elementaries![Line*3 <- Take([2], 3) <- Children <- Children], + prim_comp![Line*3 <- Take([0], 3)], + prim_comp![Line*3 <- Take([1], 3) <- Children], + prim_comp![Line*3 <- Take([2], 3) <- Children <- Children], ]) .unwrap(); assert_eq!(map.len_out(), 3); @@ -821,8 +821,8 @@ mod tests { #[test] fn uniform_product1() { let map = UniformProduct::new(vec![ - elementaries![Line*2 <- Edges], - elementaries![Line * 3], + prim_comp![Line*2 <- Edges], + prim_comp![Line * 3], ]); assert_eq!(map.len_out(), 6); assert_eq!(map.len_in(), 12); diff --git a/src/elementary.rs b/src/primitive.rs similarity index 82% rename from src/elementary.rs rename to src/primitive.rs index f7dead306..0fbca0835 100644 --- a/src/elementary.rs +++ b/src/primitive.rs @@ -528,9 +528,9 @@ impl UnboundedMap for UniformPoints { } } -/// An enum of elementary maps. +/// An enum of primitive maps. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Elementary { +pub enum Primitive { Transpose(Transpose), Take(Take), Slice(Slice), @@ -539,7 +539,7 @@ pub enum Elementary { UniformPoints(Offset), } -impl Elementary { +impl Primitive { #[inline] pub fn new_transpose(len1: usize, len2: usize) -> Self { Transpose::new(len1, len2).into() @@ -610,17 +610,17 @@ macro_rules! dispatch { }; (@match $self:ident; $fn:ident; $($arg:ident),*) => { match $self { - Elementary::Transpose(var) => var.$fn($($arg),*), - Elementary::Take(var) => var.$fn($($arg),*), - Elementary::Slice(var) => var.$fn($($arg),*), - Elementary::Children(var) => var.$fn($($arg),*), - Elementary::Edges(var) => var.$fn($($arg),*), - Elementary::UniformPoints(var) => var.$fn($($arg),*), + Self::Transpose(var) => var.$fn($($arg),*), + Self::Take(var) => var.$fn($($arg),*), + Self::Slice(var) => var.$fn($($arg),*), + Self::Children(var) => var.$fn($($arg),*), + Self::Edges(var) => var.$fn($($arg),*), + Self::UniformPoints(var) => var.$fn($($arg),*), } }; } -impl UnboundedMap for Elementary { +impl UnboundedMap for Primitive { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn mod_in(&self) -> usize} @@ -633,45 +633,45 @@ impl UnboundedMap for Elementary { dispatch! {fn is_index_map(&self) -> bool} } -impl AddOffset for Elementary { +impl AddOffset for Primitive { dispatch! {fn add_offset(&mut self, offset: usize)} } -impl std::fmt::Debug for Elementary { +impl std::fmt::Debug for Primitive { dispatch! {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result} } -impl From for Elementary { +impl From for Primitive { fn from(transpose: Transpose) -> Self { Self::Transpose(transpose) } } -impl From for Elementary { +impl From for Primitive { fn from(take: Take) -> Self { Self::Take(take) } } -impl From for Elementary { +impl From for Primitive { fn from(slice: Slice) -> Self { Self::Slice(slice) } } -impl From for Elementary { +impl From for Primitive { fn from(children: Children) -> Self { Self::Children(Offset(children, 0)) } } -impl From for Elementary { +impl From for Primitive { fn from(edges: Edges) -> Self { Self::Edges(Offset(edges, 0)) } } -impl From for Elementary { +impl From for Primitive { fn from(uniform_points: UniformPoints) -> Self { Self::UniformPoints(Offset(uniform_points, 0)) } @@ -870,55 +870,55 @@ where } } -/// An interface for swapping a composition of `Self` with an [`Elementary`]. -pub trait SwapElementaryComposition { +/// An interface for swapping a composition of `Self` with an [`Primitive`]. +pub trait SwapPrimitiveComposition { type Output; - /// Returns an [`Elementary`'] and a map such that the composition those is equivalent to the composition of `self` with `inner`. - fn swap_elementary_composition( + /// Returns an [`Primitive`'] and a map such that the composition those is equivalent to the composition of `self` with `inner`. + fn swap_primitive_composition( &self, - inner: &Elementary, + inner: &Primitive, stride: usize, - ) -> Option<((Elementary, usize), Self::Output)>; + ) -> Option<((Primitive, usize), Self::Output)>; } -impl SwapElementaryComposition for [Elementary] { - type Output = Vec; +impl SwapPrimitiveComposition for [Primitive] { + type Output = Vec; - fn swap_elementary_composition( + fn swap_primitive_composition( &self, - inner: &Elementary, + inner: &Primitive, stride: usize, - ) -> Option<((Elementary, usize), Self::Output)> { + ) -> Option<((Primitive, usize), Self::Output)> { if inner.is_transpose() { return None; } let mut target = inner.clone(); - let mut shifted_items: Vec = Vec::new(); - let mut queue: Vec = Vec::new(); + let mut shifted_items: Vec = Vec::new(); + let mut queue: Vec = Vec::new(); let mut stride_out = stride; let mut stride_in = stride; for mut item in self.iter().rev().cloned() { // Swap matching edges and children at the same offset. - if let Elementary::Edges(Offset(Edges(esimplex), eoffset)) = &item { - if let Elementary::Children(Offset(Children(ref mut csimplex), coffset)) = + if let Primitive::Edges(Offset(Edges(esimplex), eoffset)) = &item { + if let Primitive::Children(Offset(Children(ref mut csimplex), coffset)) = &mut target { if eoffset == coffset && esimplex.edge_dim() == csimplex.dim() { if stride_in != 1 && inner.mod_in() != 1 { shifted_items - .push(Elementary::new_transpose(stride_in, inner.mod_in())); + .push(Primitive::new_transpose(stride_in, inner.mod_in())); } shifted_items.append(&mut queue); if stride_out != 1 && inner.mod_in() != 1 { shifted_items - .push(Elementary::new_transpose(inner.mod_in(), stride_out)); + .push(Primitive::new_transpose(inner.mod_in(), stride_out)); } - shifted_items.push(Elementary::new_take( + shifted_items.push(Primitive::new_take( esimplex.swap_edges_children_map(), esimplex.nedges() * esimplex.nchildren(), )); - shifted_items.push(Elementary::Edges(Offset(Edges(*esimplex), *eoffset))); + shifted_items.push(Primitive::Edges(Offset(Edges(*esimplex), *eoffset))); *csimplex = *esimplex; stride_in = 1; stride_out = 1; @@ -969,11 +969,11 @@ impl SwapElementaryComposition for [Elementary] { } } if stride_in != 1 && target.mod_in() != 1 { - shifted_items.push(Elementary::new_transpose(stride_in, target.mod_in())); + shifted_items.push(Primitive::new_transpose(stride_in, target.mod_in())); } shifted_items.extend(queue); if stride_out != 1 && target.mod_in() != 1 { - shifted_items.push(Elementary::new_transpose(target.mod_in(), stride_out)); + shifted_items.push(Primitive::new_transpose(target.mod_in(), stride_out)); } if target.mod_out() == 1 { stride_out = 1; @@ -983,33 +983,33 @@ impl SwapElementaryComposition for [Elementary] { } } -impl SwapElementaryComposition for Vec { +impl SwapPrimitiveComposition for Vec { type Output = Self; #[inline] - fn swap_elementary_composition( + fn swap_primitive_composition( &self, - inner: &Elementary, + inner: &Primitive, stride: usize, - ) -> Option<((Elementary, usize), Self::Output)> { - (&self[..]).swap_elementary_composition(inner, stride) + ) -> Option<((Primitive, usize), Self::Output)> { + (&self[..]).swap_primitive_composition(inner, stride) } } -impl SwapElementaryComposition for WithBounds +impl SwapPrimitiveComposition for WithBounds where - M: UnboundedMap + SwapElementaryComposition, + M: UnboundedMap + SwapPrimitiveComposition, M::Output: UnboundedMap, { type Output = WithBounds; - fn swap_elementary_composition( + fn swap_primitive_composition( &self, - inner: &Elementary, + inner: &Primitive, stride: usize, - ) -> Option<((Elementary, usize), Self::Output)> { + ) -> Option<((Primitive, usize), Self::Output)> { self.unbounded() - .swap_elementary_composition(inner, stride) + .swap_primitive_composition(inner, stride) .map(|(outer, slf)| { let dim_in = self.dim_in() - inner.delta_dim(); let len_in = self.len_in() / inner.mod_out() * inner.mod_in(); @@ -1018,53 +1018,53 @@ where } } -/// Return type of [`AllElementaryDecompositions::all_elementary_decompositions()`]. -pub type ElementaryDecompositionIter<'a, T> = - Box + 'a>; +/// Return type of [`AllPrimitiveDecompositions::all_primitive_decompositions()`]. +pub type PrimitiveDecompositionIter<'a, T> = + Box + 'a>; -/// An interface for iterating over all possible decompositions into [`Elementary`] and `Self`. -pub trait AllElementaryDecompositions: Sized { - /// Return an iterator over all possible decompositions into `Self` and an [`Elementary`]. +/// An interface for iterating over all possible decompositions into [`Primitive`] and `Self`. +pub trait AllPrimitiveDecompositions: Sized { + /// Return an iterator over all possible decompositions into `Self` and an [`Primitive`]. /// /// # Examples /// /// ``` /// use nutils_test::simplex::Simplex::*; - /// use nutils_test::elementary::{Elementary, AllElementaryDecompositions as _}; - /// use nutils_test::elementaries; - /// let map = elementaries![Triangle*2 <- Edges <- Children]; - /// let mut iter = map.all_elementary_decompositions(); + /// use nutils_test::primitive::{Primitive, AllPrimitiveDecompositions as _}; + /// use nutils_test::prim_comp; + /// let map = prim_comp![Triangle*2 <- Edges <- Children]; + /// let mut iter = map.all_primitive_decompositions(); /// assert_eq!( /// iter.next(), - /// Some(((Elementary::new_edges(Triangle), 1), elementaries![Line*6 <- Children]))); + /// Some(((Primitive::new_edges(Triangle), 1), prim_comp![Line*6 <- Children]))); /// assert_eq!( /// iter.next(), /// Some(( - /// (Elementary::new_children(Triangle), 1), - /// elementaries![Triangle*8 <- Edges <- Take([3, 6, 1, 7, 2, 5], 12)], + /// (Primitive::new_children(Triangle), 1), + /// prim_comp![Triangle*8 <- Edges <- Take([3, 6, 1, 7, 2, 5], 12)], /// )) /// ); /// assert_eq!(iter.next(), None); /// ``` - fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self>; + fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self>; } -impl AllElementaryDecompositions for Vec { - fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self> { +impl AllPrimitiveDecompositions for Vec { + fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { let mut splits = Vec::new(); for (i, item) in self.iter().enumerate() { - if let Some((outer, mut inner)) = (&self[..i]).swap_elementary_composition(item, 1) { + if let Some((outer, mut inner)) = (&self[..i]).swap_primitive_composition(item, 1) { inner.extend(self[i + 1..].iter().cloned()); splits.push((outer, inner)); } - if let Elementary::Edges(Offset(Edges(Simplex::Line), offset)) = item { - let mut children = Elementary::new_children(Simplex::Line); + if let Primitive::Edges(Offset(Edges(Simplex::Line), offset)) = item { + let mut children = Primitive::new_children(Simplex::Line); children.add_offset(*offset); if let Some((outer, mut inner)) = - (&self[..i]).swap_elementary_composition(&children, 1) + (&self[..i]).swap_primitive_composition(&children, 1) { inner.push(item.clone()); - inner.push(Elementary::new_take( + inner.push(Primitive::new_take( Simplex::Line.swap_edges_children_map(), Simplex::Line.nedges() * Simplex::Line.nchildren(), )); @@ -1077,14 +1077,14 @@ impl AllElementaryDecompositions for Vec { } } -impl AllElementaryDecompositions for WithBounds +impl AllPrimitiveDecompositions for WithBounds where - M: UnboundedMap + AllElementaryDecompositions, + M: UnboundedMap + AllPrimitiveDecompositions, { - fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self> { + fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { Box::new( self.unbounded() - .all_elementary_decompositions() + .all_primitive_decompositions() .into_iter() .map(|(outer, unbounded)| { ( @@ -1096,37 +1096,37 @@ where } } -/// Create a bounded composition of elementary maps. +/// Create a bounded composition of primitive maps. /// /// # Syntax /// /// The arguments of the macro are separated by `<-`, indicating the direction /// of the map. The first argument is a simplex (`Triangle`, `Line`) or /// `Point`, multiplied with the output length of the map. The remaining arguments -/// are elementary maps: `Children`, `Edges`, `Transpose(len1, len2)` or `Take(indices, len)`. +/// are primitive maps: `Children`, `Edges`, `Transpose(len1, len2)` or `Take(indices, len)`. /// /// # Examples /// /// ``` -/// use nutils_test::elementaries; +/// use nutils_test::prim_comp; /// use nutils_test::simplex::Simplex::*; -/// elementaries![Line*2 <- Children <- Edges]; +/// prim_comp![Line*2 <- Children <- Edges]; /// ``` #[macro_export] -macro_rules! elementaries { +macro_rules! prim_comp { (Point*$len_out:literal $($tail:tt)*) => {{ - use $crate::elementary::{Elementary, WithBounds}; + use $crate::primitive::{Primitive, WithBounds}; #[allow(unused_mut)] - let mut comp: Vec = Vec::new(); - $crate::elementaries!{@adv comp, Point; $($tail)*} + let mut comp: Vec = Vec::new(); + $crate::prim_comp!{@adv comp, Point; $($tail)*} WithBounds::from_output(comp, 0, $len_out).unwrap() }}; ($simplex:tt*$len_out:literal $($tail:tt)*) => {{ - use $crate::elementary::{Elementary, WithBounds}; + use $crate::primitive::{Primitive, WithBounds}; #[allow(unused_mut)] - let mut comp: Vec = Vec::new(); - $crate::elementaries!{@adv comp, $simplex; $($tail)*} - let dim_out = $crate::elementaries!(@dim $simplex); + let mut comp: Vec = Vec::new(); + $crate::prim_comp!{@adv comp, $simplex; $($tail)*} + let dim_out = $crate::prim_comp!(@dim $simplex); WithBounds::from_output(comp, dim_out, $len_out).unwrap() }}; (@dim Point) => {0}; @@ -1134,24 +1134,24 @@ macro_rules! elementaries { (@dim Triangle) => {2}; (@adv $comp:ident, $simplex:tt;) => {}; (@adv $comp:ident, $simplex:tt; <- Children $($tail:tt)*) => {{ - $comp.push(Elementary::new_children($crate::simplex::Simplex::$simplex)); - $crate::elementaries!{@adv $comp, $simplex; $($tail)*} + $comp.push(Primitive::new_children($crate::simplex::Simplex::$simplex)); + $crate::prim_comp!{@adv $comp, $simplex; $($tail)*} }}; (@adv $comp:ident, Triangle; <- Edges $($tail:tt)*) => {{ - $comp.push(Elementary::new_edges($crate::simplex::Simplex::Triangle)); - $crate::elementaries!{@adv $comp, Line; $($tail)*} + $comp.push(Primitive::new_edges($crate::simplex::Simplex::Triangle)); + $crate::prim_comp!{@adv $comp, Line; $($tail)*} }}; (@adv $comp:ident, Line; <- Edges $($tail:tt)*) => {{ - $comp.push(Elementary::new_edges($crate::simplex::Simplex::Line)); - $crate::elementaries!{@adv $comp, Point; $($tail)*} + $comp.push(Primitive::new_edges($crate::simplex::Simplex::Line)); + $crate::prim_comp!{@adv $comp, Point; $($tail)*} }}; (@adv $comp:ident, $simplex:tt; <- Transpose($len1:expr, $len2:expr) $($tail:tt)*) => {{ - $comp.push(Elementary::new_transpose($len1, $len2)); - $crate::elementaries!{@adv $comp, $simplex; $($tail)*} + $comp.push(Primitive::new_transpose($len1, $len2)); + $crate::prim_comp!{@adv $comp, $simplex; $($tail)*} }}; (@adv $comp:ident, $simplex:tt; <- Take($indices:expr, $len:expr) $($tail:tt)*) => {{ - $comp.push(Elementary::new_take($indices.to_vec(), $len)); - $crate::elementaries!{@adv $comp, $simplex; $($tail)*} + $comp.push(Primitive::new_take($indices.to_vec(), $len)); + $crate::prim_comp!{@adv $comp, $simplex; $($tail)*} }}; } @@ -1199,7 +1199,7 @@ mod tests { #[test] fn apply_transpose() { - let item = Elementary::new_transpose(3, 2); + let item = Primitive::new_transpose(3, 2); assert_map_apply!(item, 0, 0); assert_map_apply!(item, 1, 3); assert_map_apply!(item, 2, 1); @@ -1212,7 +1212,7 @@ mod tests { #[test] fn apply_take() { - let item = Elementary::new_take(vec![4, 1, 2], 5); + let item = Primitive::new_take(vec![4, 1, 2], 5); assert_map_apply!(item, 0, 4); assert_map_apply!(item, 1, 1); assert_map_apply!(item, 2, 2); @@ -1223,7 +1223,7 @@ mod tests { #[test] fn apply_children_line() { - let mut item = Elementary::new_children(Line); + let mut item = Primitive::new_children(Line); assert_map_apply!(item, 0, [[0.0], [1.0]], 0, [[0.0], [0.5]]); assert_map_apply!(item, 1, [[0.0], [1.0]], 0, [[0.5], [1.0]]); assert_map_apply!(item, 2, [[0.0], [1.0]], 1, [[0.0], [0.5]]); @@ -1240,7 +1240,7 @@ mod tests { #[test] fn apply_edges_line() { - let mut item = Elementary::new_edges(Line); + let mut item = Primitive::new_edges(Line); assert_map_apply!(item, 0, [[]], 0, [[1.0]]); assert_map_apply!(item, 1, [[]], 0, [[0.0]]); assert_map_apply!(item, 2, [[]], 1, [[1.0]]); @@ -1251,7 +1251,7 @@ mod tests { #[test] fn apply_uniform_points() { - let mut item = Elementary::new_uniform_points(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); + let mut item = Primitive::new_uniform_points(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2); assert_map_apply!(item, 0, [[]], 0, [[1.0, 2.0]]); assert_map_apply!(item, 1, [[]], 0, [[3.0, 4.0]]); assert_map_apply!(item, 2, [[]], 0, [[5.0, 6.0]]); @@ -1282,27 +1282,27 @@ mod tests { #[test] fn unapply_indices_transpose() { - assert_unapply!(Elementary::new_transpose(3, 2)); + assert_unapply!(Primitive::new_transpose(3, 2)); } #[test] fn unapply_indices_take() { - assert_unapply!(Elementary::new_take(vec![4, 1], 5)); + assert_unapply!(Primitive::new_take(vec![4, 1], 5)); } #[test] fn unapply_indices_children() { - assert_unapply!(Elementary::new_children(Triangle)); + assert_unapply!(Primitive::new_children(Triangle)); } #[test] fn unapply_indices_edges() { - assert_unapply!(Elementary::new_edges(Triangle)); + assert_unapply!(Primitive::new_edges(Triangle)); } #[test] fn unapply_indices_uniform_points() { - assert_unapply!(Elementary::new_uniform_points( + assert_unapply!(Primitive::new_uniform_points( vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0], 2 )); @@ -1310,8 +1310,8 @@ mod tests { macro_rules! assert_equiv_maps { ($a:expr, $b:expr $(, $simplex:ident)*) => {{ - let a: &[Elementary] = &$a; - let b: &[Elementary] = &$b; + let a: &[Primitive] = &$a; + let b: &[Primitive] = &$b; let dim_in = 0 $(+ $simplex.dim())*; let dim_out = a.dim_out(); println!("a: {a:?}"); @@ -1346,13 +1346,13 @@ mod tests { }}; } - macro_rules! assert_swap_elementary_composition { + macro_rules! assert_swap_primitive_composition { ($($item:expr),*; $($simplex:ident),*) => {{ - let unshifted = [$(Elementary::from($item),)*]; - let ((litem, lstride), lchain) = (&unshifted[..unshifted.len() - 1]).swap_elementary_composition(&unshifted.last().unwrap(), 1).unwrap(); - let mut shifted: Vec = Vec::new(); + let unshifted = [$(Primitive::from($item),)*]; + let ((litem, lstride), lchain) = (&unshifted[..unshifted.len() - 1]).swap_primitive_composition(&unshifted.last().unwrap(), 1).unwrap(); + let mut shifted: Vec = Vec::new(); if lstride != 1 { - shifted.push(Elementary::new_transpose(lstride, litem.mod_out())); + shifted.push(Primitive::new_transpose(lstride, litem.mod_out())); } shifted.push(litem); shifted.extend(lchain.into_iter()); @@ -1361,32 +1361,32 @@ mod tests { } #[test] - fn swap_elementary_composition() { - assert_swap_elementary_composition!( - Transpose::new(4, 3), Elementary::new_take(vec![0, 1], 3); + fn swap_primitive_composition() { + assert_swap_primitive_composition!( + Transpose::new(4, 3), Primitive::new_take(vec![0, 1], 3); ); - assert_swap_elementary_composition!( - Transpose::new(3, 5), Transpose::new(5, 4*3), Elementary::new_take(vec![0, 1], 3); + assert_swap_primitive_composition!( + Transpose::new(3, 5), Transpose::new(5, 4*3), Primitive::new_take(vec![0, 1], 3); ); - assert_swap_elementary_composition!( - Transpose::new(5, 4), Transpose::new(5*4, 3), Elementary::new_take(vec![0, 1], 3); + assert_swap_primitive_composition!( + Transpose::new(5, 4), Transpose::new(5*4, 3), Primitive::new_take(vec![0, 1], 3); ); - assert_swap_elementary_composition!( - {let mut elem = Elementary::new_children(Line); elem.add_offset(1); elem}, - Elementary::new_children(Line); + assert_swap_primitive_composition!( + {let mut elem = Primitive::new_children(Line); elem.add_offset(1); elem}, + Primitive::new_children(Line); Line, Line ); - assert_swap_elementary_composition!( - Elementary::new_edges(Line), Elementary::new_children(Line); + assert_swap_primitive_composition!( + Primitive::new_edges(Line), Primitive::new_children(Line); Line ); - assert_swap_elementary_composition!( - Elementary::new_children(Line), - {let mut elem = Elementary::new_edges(Line); elem.add_offset(1); elem}; + assert_swap_primitive_composition!( + Primitive::new_children(Line), + {let mut elem = Primitive::new_edges(Line); elem.add_offset(1); elem}; Line ); - assert_swap_elementary_composition!( - Elementary::new_edges(Triangle), Elementary::new_children(Line); + assert_swap_primitive_composition!( + Primitive::new_edges(Triangle), Primitive::new_children(Line); Line ); } diff --git a/src/relative.rs b/src/relative.rs index 4d74931e0..48b700973 100644 --- a/src/relative.rs +++ b/src/relative.rs @@ -1,6 +1,6 @@ -use crate::elementary::{ - AllElementaryDecompositions, Elementary, ElementaryDecompositionIter, - SwapElementaryComposition, UnboundedMap, WithBounds, Identity, Slice, +use crate::primitive::{ + AllPrimitiveDecompositions, Primitive, PrimitiveDecompositionIter, + SwapPrimitiveComposition, UnboundedMap, WithBounds, Identity, Slice, }; use crate::ops::{BinaryComposition, BinaryProduct, UniformProduct, UniformConcat}; use crate::util::ReplaceNthIter as _; @@ -9,27 +9,27 @@ use std::collections::BTreeMap; use std::rc::Rc; use std::iter; -impl AllElementaryDecompositions for UniformProduct> +impl AllPrimitiveDecompositions for UniformProduct> where - M: Map + AllElementaryDecompositions + Clone, + M: Map + AllPrimitiveDecompositions + Clone, { - fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self> { + fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { Box::new( self.iter() .enumerate() .zip(self.offsets_out()) .zip(self.strides_out()) .flat_map(move |(((iprod, term), prod_offset), prod_stride)| { - term.all_elementary_decompositions().into_iter().map( - move |((mut elmtry, mut stride), inner)| { - elmtry.add_offset(prod_offset); - if elmtry.mod_out() == 1 { + term.all_primitive_decompositions().into_iter().map( + move |((mut prim, mut stride), inner)| { + prim.add_offset(prod_offset); + if prim.mod_out() == 1 { stride = 1; } else { stride *= prod_stride; } let product = self.iter().cloned().replace_nth(iprod, inner).collect(); - ((elmtry, stride), product) + ((prim, stride), product) }, ) }), @@ -37,29 +37,29 @@ where } } -impl AllElementaryDecompositions for BinaryProduct +impl AllPrimitiveDecompositions for BinaryProduct where - M0: Map + AllElementaryDecompositions + Clone, - M1: Map + AllElementaryDecompositions + Clone, + M0: Map + AllPrimitiveDecompositions + Clone, + M1: Map + AllPrimitiveDecompositions + Clone, { - fn all_elementary_decompositions<'a>(&'a self) -> ElementaryDecompositionIter<'a, Self> { + fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { let first = self .first() - .all_elementary_decompositions() + .all_primitive_decompositions() .into_iter() - .map(|((elmtry, mut stride), first)| { + .map(|((prim, mut stride), first)| { stride *= self.second().len_out(); let product = BinaryProduct::new(first, self.second().clone()); - ((elmtry, stride), product) + ((prim, stride), product) }); let second = self .second() - .all_elementary_decompositions() + .all_primitive_decompositions() .into_iter() - .map(|((mut elmtry, stride), second)| { - elmtry.add_offset(self.first().dim_out()); + .map(|((mut prim, stride), second)| { + prim.add_offset(self.first().dim_out()); let product = BinaryProduct::new(self.first().clone(), second); - ((elmtry, stride), product) + ((prim, stride), product) }); Box::new(first.chain(second)) } @@ -67,27 +67,27 @@ where // TODO: UniformComposition? -impl AllElementaryDecompositions for BinaryComposition +impl AllPrimitiveDecompositions for BinaryComposition where - Outer: Map + AllElementaryDecompositions + SwapElementaryComposition, - Inner: Map + AllElementaryDecompositions + Clone, + Outer: Map + AllPrimitiveDecompositions + SwapPrimitiveComposition, + Inner: Map + AllPrimitiveDecompositions + Clone, { - fn all_elementary_decompositions<'a>( + fn all_primitive_decompositions<'a>( &'a self, - ) -> Box + 'a> { + ) -> Box + 'a> { let split_outer = self .outer() - .all_elementary_decompositions() + .all_primitive_decompositions() .into_iter() - .map(|(elmtry, outer)| (elmtry, Self::new(outer, self.inner().clone()).unwrap())); + .map(|(prim, outer)| (prim, Self::new(outer, self.inner().clone()).unwrap())); let split_inner = self .inner() - .all_elementary_decompositions() + .all_primitive_decompositions() .into_iter() - .filter_map(|((elmtry, stride), inner)| { + .filter_map(|((prim, stride), inner)| { self.outer() - .swap_elementary_composition(&elmtry, stride) - .map(|(elmtry, outer)| (elmtry, Self::new(outer, inner).unwrap())) + .swap_primitive_composition(&prim, stride) + .map(|(prim, outer)| (prim, Self::new(outer, inner).unwrap())) }); Box::new(split_outer.chain(split_inner)) } @@ -101,44 +101,44 @@ where /// # Examples /// /// ``` -/// use nutils_test::elementaries; -/// let map1 = elementaries![Line*1 <- Children <- Children <- Edges]; -/// let map2 = elementaries![Line*1 <- Children <- Edges]; +/// use nutils_test::prim_comp; +/// let map1 = prim_comp![Line*1 <- Children <- Children <- Edges]; +/// let map2 = prim_comp![Line*1 <- Children <- Edges]; /// let (common, rem1, rem2) = nutils_test::relative::decompose_common(map1, map2); -/// assert_eq!(common, elementaries![Line*1 <- Children <- Children <- Edges]); -/// assert_eq!(rem1, elementaries![Point*8]); -/// assert_eq!(rem2, elementaries![Point*8 <- Take([2, 1], 4)]); +/// assert_eq!(common, prim_comp![Line*1 <- Children <- Children <- Edges]); +/// assert_eq!(rem1, prim_comp![Point*8]); +/// assert_eq!(rem2, prim_comp![Point*8 <- Take([2, 1], 4)]); /// ``` /// /// ``` -/// use nutils_test::elementaries; -/// let map1 = elementaries![Triangle*1 <- Children]; -/// let map2 = elementaries![Triangle*1 <- Edges <- Children]; +/// use nutils_test::prim_comp; +/// let map1 = prim_comp![Triangle*1 <- Children]; +/// let map2 = prim_comp![Triangle*1 <- Edges <- Children]; /// let (common, rem1, rem2) = nutils_test::relative::decompose_common(map1, map2); -/// assert_eq!(common, elementaries![Triangle*1 <- Children]); -/// assert_eq!(rem1, elementaries![Triangle*4]); -/// assert_eq!(rem2, elementaries![Triangle*4 <- Edges <- Take([3, 6, 1, 7, 2, 5], 12)]); +/// assert_eq!(common, prim_comp![Triangle*1 <- Children]); +/// assert_eq!(rem1, prim_comp![Triangle*4]); +/// assert_eq!(rem2, prim_comp![Triangle*4 <- Edges <- Take([3, 6, 1, 7, 2, 5], 12)]); /// ``` -pub fn decompose_common(mut map1: M1, mut map2: M2) -> (WithBounds>, M1, M2) +pub fn decompose_common(mut map1: M1, mut map2: M2) -> (WithBounds>, M1, M2) where - M1: Map + AllElementaryDecompositions, - M2: Map + AllElementaryDecompositions, + M1: Map + AllPrimitiveDecompositions, + M2: Map + AllPrimitiveDecompositions, { // TODO: check output dimensions? and return error if dimensions don't match? assert_eq!(map1.len_out(), map2.len_out()); assert_eq!(map1.dim_out(), map2.dim_out()); let mut common = Vec::new(); while !map1.is_identity() && !map2.is_identity() { - let mut outers2: BTreeMap<_, _> = map2.all_elementary_decompositions().collect(); + let mut outers2: BTreeMap<_, _> = map2.all_primitive_decompositions().collect(); (map1, map2) = if let Some(((outer, stride), map1, map2)) = map1 - .all_elementary_decompositions() + .all_primitive_decompositions() .filter_map(|(key, t1)| outers2.remove(&key).map(|t2| (key, t1, t2))) .next() { let outer_mod_out = outer.mod_out(); common.push(outer); if stride != 1 { - common.push(Elementary::new_transpose(stride, outer_mod_out)); + common.push(Primitive::new_transpose(stride, outer_mod_out)); } (map1, map2) } else { @@ -153,8 +153,8 @@ where fn partial_relative_to(source: Source, target: Target) -> PartialRelative where - Source: Map + AllElementaryDecompositions, - Target: Map + AllElementaryDecompositions, + Source: Map + AllPrimitiveDecompositions, + Target: Map + AllPrimitiveDecompositions, { let (_, rem, rel) = decompose_common(target, source); if rem.is_identity() { @@ -250,7 +250,7 @@ impl Map for Relative { // dispatch! {fn add_offset(&mut self, offset: usize)} //} -impl RelativeTo for WithBounds> { +impl RelativeTo for WithBounds> { type Output = Self; fn relative_to(&self, target: &Self) -> Option { @@ -305,7 +305,7 @@ impl UnapplyIndicesData for IndexOutIn { pub struct RelativeToConcat { rels: Vec, index_map: Rc>, - //common: Vec, + //common: Vec, len_out: usize, len_in: usize, dim_in: usize, @@ -386,8 +386,8 @@ impl Map for RelativeToConcat { impl RelativeTo> for Source where - Source: Map + AllElementaryDecompositions + Clone, - Target: Map + AllElementaryDecompositions + Clone, + Source: Map + AllPrimitiveDecompositions + Clone, + Target: Map + AllPrimitiveDecompositions + Clone, { type Output = Relative; @@ -463,7 +463,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::elementaries; + use crate::prim_comp; use crate::simplex::Simplex::*; use approx::assert_abs_diff_eq; use std::iter; @@ -504,22 +504,22 @@ mod tests { #[test] fn decompose_common_vec() { - let map1 = elementaries![Line*2 <- Children <- Children]; - let map2 = elementaries![Line*2 <- Children <- Take([0, 2], 4)]; + let map1 = prim_comp![Line*2 <- Children <- Children]; + let map2 = prim_comp![Line*2 <- Children <- Take([0, 2], 4)]; assert_eq!( decompose_common(map1, map2), ( - elementaries![Line*2 <- Children], - elementaries![Line*4 <- Children], - elementaries![Line*4 <- Take([0, 2], 4)], + prim_comp![Line*2 <- Children], + prim_comp![Line*4 <- Children], + prim_comp![Line*4 <- Take([0, 2], 4)], ) ); } #[test] fn decompose_common_product() { - let map1 = elementaries![Line*2 <- Children <- Children]; - let map2 = elementaries![Line*2 <- Children <- Take([0, 2], 4)]; + let map1 = prim_comp![Line*2 <- Children <- Children]; + let map2 = prim_comp![Line*2 <- Children <- Take([0, 2], 4)]; let (common, rel1, rel2) = decompose_common(map1.clone(), map2.clone()); assert!(!common.is_identity()); assert_equiv_maps!( @@ -532,18 +532,18 @@ mod tests { map2.clone(), Line ); - //assert_eq!(rel1, BinaryProduct::new(elementaries![Line*4 <- Children], elementaries![Line*4 <- Take([0, 2], 4)])); - //assert_eq!(rel2, BinaryProduct::new(elementaries![Line*4 <- Take([0, 2], 4)], elementaries![Line*4 <- Children])); - //assert_eq!(common, elementaries![Line*2]); + //assert_eq!(rel1, BinaryProduct::new(prim_comp![Line*4 <- Children], prim_comp![Line*4 <- Take([0, 2], 4)])); + //assert_eq!(rel2, BinaryProduct::new(prim_comp![Line*4 <- Take([0, 2], 4)], prim_comp![Line*4 <- Children])); + //assert_eq!(common, prim_comp![Line*2]); // WithBounds { map: [Offset(Children(Line), 1), Transpose(2, 1), Offset(Children(Line), 0)], dim_in: 2, delta_dim: 0, len_in: 16, len_out: 4 } } // #[test] // fn rel_to_single() { -// let a1 = elementaries![Line*2 <- Children <- Take([0, 2], 4)]; -// let a2 = elementaries![Line*2 <- Children <- Take([1, 3], 4) <- Children]; +// let a1 = prim_comp![Line*2 <- Children <- Take([0, 2], 4)]; +// let a2 = prim_comp![Line*2 <- Children <- Take([1, 3], 4) <- Children]; // let a = BinaryConcat::new(a1, a2).unwrap(); -// let b = elementaries![Line*2 <- Children <- Children <- Children]; +// let b = prim_comp![Line*2 <- Children <- Children <- Children]; // assert_equiv_maps!( // BinaryComposition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), // b, From bd04d2d949670caf4393581b5d31efc9a1f4bdfc Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Mon, 4 Jul 2022 16:49:46 +0200 Subject: [PATCH 33/45] WIP --- src/lib.rs | 8 +- src/ops.rs | 5 +- src/primitive.rs | 12 +- src/relative.rs | 57 ++-- src/simplex.rs | 10 +- src/tesselation.rs | 647 ++++++++++++++++++++++++--------------------- src/topology.rs | 171 +++++------- src/util.rs | 31 +++ 8 files changed, 488 insertions(+), 453 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1b6164bf2..297882bd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,18 @@ -pub mod primitive; pub mod finite_f64; pub mod ops; +pub mod primitive; pub mod relative; pub mod simplex; +pub mod tesselation; +pub mod topology; mod util; -//pub mod tesselation; -//pub mod topology; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { Empty, DimensionMismatch, LengthMismatch, + DimensionZeroHasNoEdges, } impl std::error::Error for Error {} @@ -22,6 +23,7 @@ impl std::fmt::Display for Error { Self::Empty => write!(f, "The input array is empty."), Self::DimensionMismatch => write!(f, "The dimensions of the maps differ."), Self::LengthMismatch => write!(f, "The lengths of the maps differ."), + Self::DimensionZeroHasNoEdges => write!(f, "Dimension zero has no edges."), } } } diff --git a/src/ops.rs b/src/ops.rs index f4396edc7..726b501b0 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -820,10 +820,7 @@ mod tests { #[test] fn uniform_product1() { - let map = UniformProduct::new(vec![ - prim_comp![Line*2 <- Edges], - prim_comp![Line * 3], - ]); + let map = UniformProduct::new(vec![prim_comp![Line*2 <- Edges], prim_comp![Line * 3]]); assert_eq!(map.len_out(), 6); assert_eq!(map.len_in(), 12); assert_eq!(map.dim_in(), 1); diff --git a/src/primitive.rs b/src/primitive.rs index 0fbca0835..bda90ec40 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -573,6 +573,12 @@ impl Primitive { _ => None, } } + pub fn with_offset(mut self, offset: usize) -> Self { + if let Some(self_offset) = self.offset_mut() { + *self_offset = offset + } + self + } #[inline] const fn is_transpose(&self) -> bool { matches!(self, Self::Transpose(_)) @@ -906,8 +912,7 @@ impl SwapPrimitiveComposition for [Primitive] { { if eoffset == coffset && esimplex.edge_dim() == csimplex.dim() { if stride_in != 1 && inner.mod_in() != 1 { - shifted_items - .push(Primitive::new_transpose(stride_in, inner.mod_in())); + shifted_items.push(Primitive::new_transpose(stride_in, inner.mod_in())); } shifted_items.append(&mut queue); if stride_out != 1 && inner.mod_in() != 1 { @@ -1019,8 +1024,7 @@ where } /// Return type of [`AllPrimitiveDecompositions::all_primitive_decompositions()`]. -pub type PrimitiveDecompositionIter<'a, T> = - Box + 'a>; +pub type PrimitiveDecompositionIter<'a, T> = Box + 'a>; /// An interface for iterating over all possible decompositions into [`Primitive`] and `Self`. pub trait AllPrimitiveDecompositions: Sized { diff --git a/src/relative.rs b/src/relative.rs index 48b700973..ac51f07b5 100644 --- a/src/relative.rs +++ b/src/relative.rs @@ -1,13 +1,13 @@ +use crate::ops::{BinaryComposition, BinaryProduct, UniformComposition, UniformConcat, UniformProduct}; use crate::primitive::{ - AllPrimitiveDecompositions, Primitive, PrimitiveDecompositionIter, - SwapPrimitiveComposition, UnboundedMap, WithBounds, Identity, Slice, + AllPrimitiveDecompositions, Identity, Primitive, PrimitiveDecompositionIter, Slice, + SwapPrimitiveComposition, UnboundedMap, WithBounds, }; -use crate::ops::{BinaryComposition, BinaryProduct, UniformProduct, UniformConcat}; use crate::util::ReplaceNthIter as _; use crate::{AddOffset, Map, UnapplyIndicesData}; use std::collections::BTreeMap; -use std::rc::Rc; use std::iter; +use std::rc::Rc; impl AllPrimitiveDecompositions for UniformProduct> where @@ -43,15 +43,13 @@ where M1: Map + AllPrimitiveDecompositions + Clone, { fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { - let first = self - .first() - .all_primitive_decompositions() - .into_iter() - .map(|((prim, mut stride), first)| { + let first = self.first().all_primitive_decompositions().into_iter().map( + |((prim, mut stride), first)| { stride *= self.second().len_out(); let product = BinaryProduct::new(first, self.second().clone()); ((prim, stride), product) - }); + }, + ); let second = self .second() .all_primitive_decompositions() @@ -196,7 +194,7 @@ pub enum Relative { Identity(WithBounds), Slice(WithBounds), Map(M), - Concat(UniformConcat), + Composition(UniformComposition), RelativeToConcat(RelativeToConcat), } @@ -222,7 +220,7 @@ macro_rules! dispatch { Self::Identity(var) => var.$fn($($arg),*), Self::Slice(var) => var.$fn($($arg),*), Self::Map(var) => var.$fn($($arg),*), - Self::Concat(var) => var.$fn($($arg),*), + Self::Composition(var) => var.$fn($($arg),*), Self::RelativeToConcat(var) => var.$fn($($arg),*), } } @@ -358,9 +356,7 @@ impl Map for RelativeToConcat { self.index_map .iter() .enumerate() - .filter_map(|(iin, (iout, _))| { - (*iout == index.get()).then(|| index.set(iin)) - }), + .filter_map(|(iin, (iout, _))| (*iout == index.get()).then(|| index.set(iin))), ); } //self.common.unapply_indices(&in_indices) @@ -399,10 +395,13 @@ where match partial_relative_to(self.clone(), target) { PartialRelative::AllSameOrder(rel) => { let slice = Slice::new(offset, rel.len_out(), targets.len_in()); - let slice = WithBounds::from_input(slice, rel.dim_out(), rel.len_out()).unwrap(); + let slice = + WithBounds::from_input(slice, rel.dim_out(), rel.len_out()).unwrap(); let slice = Relative::Slice(slice); let rel = Relative::Map(rel); - return Some(Relative::Concat(UniformConcat::new_unchecked(vec![slice, rel]))); + return Some(Relative::Composition(UniformComposition::new_unchecked(vec![ + slice, rel, + ]))); } PartialRelative::Some(rel, indices) => { rels_indices.push((rel, offset, indices)); @@ -538,16 +537,16 @@ mod tests { // WithBounds { map: [Offset(Children(Line), 1), Transpose(2, 1), Offset(Children(Line), 0)], dim_in: 2, delta_dim: 0, len_in: 16, len_out: 4 } } -// #[test] -// fn rel_to_single() { -// let a1 = prim_comp![Line*2 <- Children <- Take([0, 2], 4)]; -// let a2 = prim_comp![Line*2 <- Children <- Take([1, 3], 4) <- Children]; -// let a = BinaryConcat::new(a1, a2).unwrap(); -// let b = prim_comp![Line*2 <- Children <- Children <- Children]; -// assert_equiv_maps!( -// BinaryComposition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), -// b, -// Line -// ); -// } + // #[test] + // fn rel_to_single() { + // let a1 = prim_comp![Line*2 <- Children <- Take([0, 2], 4)]; + // let a2 = prim_comp![Line*2 <- Children <- Take([1, 3], 4) <- Children]; + // let a = BinaryConcat::new(a1, a2).unwrap(); + // let b = prim_comp![Line*2 <- Children <- Children <- Children]; + // assert_equiv_maps!( + // BinaryComposition::new(b.relative_to(&a).unwrap(), a.clone()).unwrap(), + // b, + // Line + // ); + // } } diff --git a/src/simplex.rs b/src/simplex.rs index 3a693b971..3a5e7748b 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -182,10 +182,7 @@ impl Simplex { Self::Line => { for coordinate in coordinates.chunks_mut(stride) { let coordinate = &mut coordinate[offset..]; - coordinate.copy_within( - self.edge_dim() as usize..coordinate.len() - 1, - self.dim() as usize, - ); + coordinate.copy_within(self.edge_dim()..coordinate.len() - 1, self.dim()); coordinate[0] = (1 - index % 2) as f64; } index / 2 @@ -193,10 +190,7 @@ impl Simplex { Self::Triangle => { for coordinate in coordinates.chunks_mut(stride) { let coordinate = &mut coordinate[offset..]; - coordinate.copy_within( - self.edge_dim() as usize..coordinate.len() - 1, - self.dim() as usize, - ); + coordinate.copy_within(self.edge_dim()..coordinate.len() - 1, self.dim()); match index % 3 { 0 => { coordinate[1] = coordinate[0]; diff --git a/src/tesselation.rs b/src/tesselation.rs index a43c2be89..12775970d 100644 --- a/src/tesselation.rs +++ b/src/tesselation.rs @@ -1,371 +1,404 @@ -use crate::elementary::{Elementary, PushElementary}; -use crate::ops::{Concatenation, WithBoundsError}; +use crate::ops::UniformConcat; +use crate::primitive::{ + AllPrimitiveDecompositions, Primitive, PrimitiveDecompositionIter, WithBounds, +}; +use crate::relative::RelativeTo; use crate::simplex::Simplex; -use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; +use crate::util::{ReplaceNthIter, SkipNthIter}; +use crate::{AddOffset, Error, Map, UnapplyIndicesData}; +use std::iter; +use std::ops::Mul; -struct UniformTesselation { +#[derive(Debug, Clone, PartialEq)] +pub struct UniformTesselation { shapes: Vec, - map: WithBounds>, + map: WithBounds>, } impl UniformTesselation { - fn identity(shapes: Vec, len: usize) -> Self { + pub fn identity(shapes: Vec, len: usize) -> Self { let dim = shapes.iter().map(|simplex| simplex.dim()).sum(); - let map = WithBounds::from_output(vec![], dim, len); + let map = WithBounds::new_unchecked(vec![], dim, len); Self { shapes, map } } - fn children(&self) -> Self { - let mut map = self.map.clone(); - let mut offset = 0; - for shape in &self.shapes { - let item = Elementary::new_children(shape); - item.add_offset(offset); - map.push(item); - offset += shape.dim(); - } - Self { shapes: self.shapes.clone(), map } + fn extend(&self, primitives: Primitives, shapes: Vec, len: usize) -> Self + where + Primitives: IntoIterator, + { + let map = self + .map + .unbounded() + .iter() + .cloned() + .chain(primitives) + .collect(); + let dim = shapes.iter().map(|shape| shape.dim()).sum(); + let map = WithBounds::new_unchecked(map, dim, len); + Self { shapes, map } } - fn edges(&self) -> Result { - if self.shapes().is_empty() { - return Err("dimension zero"); + fn offsets(&self) -> impl Iterator + '_ { + self.shapes.iter().scan(0, |offset, shape| { + let item = *offset; + *offset += shape.dim(); + Some(item) + }) + } + pub fn take(&self, indices: &[usize]) -> Self { + assert!(indices.windows(2).all(|pair| pair[0] < pair[1])); + if let Some(last) = indices.last() { + assert!(*last < self.len_in()); } - let mut map = self.map.clone(); - let mut shapes = Vec::with_capacity(self.shapes.len()); - let mut offset = 0; - for shape in &self.shapes { - let item = Elementary::new_edges(shape); - item.add_offset(offset); - map.push(item); - offset += shape.edge_dim(); - if let Some(edge_shape) = shape.edge_shape() { - shapes.push(edge_shape); + if indices.is_empty() { + // TODO: Disallow! Skip this map in the concatenation instead. + Self { + shapes: self.shapes.clone(), + map: WithBounds::new_unchecked(Vec::new(), self.dim_in(), 0), } + } else { + let primitive = Primitive::new_take(indices, self.len_in()); + let result = self.extend([primitive], self.shapes.clone(), indices.len()); + result + } + } + pub fn children(&self) -> Self { + let primitives = self + .shapes + .iter() + .zip(self.offsets()) + .map(|(shape, offset)| Primitive::new_children(*shape).with_offset(offset)); + let nchildren: usize = self.shapes.iter().map(|shape| shape.nchildren()).product(); + self.extend(primitives, self.shapes.clone(), self.len_in() * nchildren) + } + pub fn edges(&self, ishape: usize) -> Option { + self.shapes.get(ishape).map(|shape| { + let offset = self + .shapes + .iter() + .take(ishape) + .map(|shape| shape.dim()) + .sum(); + let primitive = Primitive::new_edges(*shape).with_offset(offset); + let shapes: Vec<_> = if let Some(edge_shape) = shape.edge_simplex() { + self.shapes + .iter() + .cloned() + .replace_nth(ishape, edge_shape) + .collect() + } else { + self.shapes.iter().cloned().skip_nth(ishape).collect() + }; + self.extend([primitive], shapes, self.len_in() * shape.nedges()) + }) + } + pub fn edges_iter(&self) -> Option + '_> { + if self.shapes.is_empty() { + None + } else { + Some((0..self.shapes.len()).map(|ishape| self.edges(ishape).unwrap())) + } + } + pub fn centroids(&self) -> Self { + let primitives = self + .shapes + .iter() + .map(|shape| Primitive::new_uniform_points(shape.centroid(), shape.dim())); + self.extend(primitives, Vec::new(), self.len_in()) + } +} + +//impl Deref for UniformTesselation { +// type Target = WithBounds>; +// +// fn deref(&self) -> Self::Target { +// self.map +// } +//} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $(-> $ret:ty)? + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { + $self.map.$fn($($arg),*) } - Ok(Self { shapes, map }) + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { + $self.map.$fn($($arg),*) + } + }; +} + +impl Map for UniformTesselation { + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} + dispatch! {fn is_index_map(&self) -> bool} +} + +impl AllPrimitiveDecompositions for UniformTesselation { + fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { + Box::new(self.map.all_primitive_decompositions().map(|(prim, map)| { + ( + prim, + Self { + shapes: self.shapes.clone(), + map: map, + }, + ) + })) } } -impl Deref for UniformTesselation { - type Target = WithBounds>; +impl RelativeTo for UniformTesselation { + type Output = WithBounds>; - fn deref(&self) -> Self::Target { - self.map + fn relative_to(&self, target: &Self) -> Option { + self.map.relative_to(&target.map) } } -pub struct Tesselation(UniformConcat, Vec>); +#[derive(Debug, Clone, PartialEq)] +pub struct Tesselation(UniformConcat); impl Tesselation { + pub fn new(items: Vec) -> Result { + let items = items.into_iter().filter(|map| map.len_in() > 0).collect(); + Ok(Self(UniformConcat::new(items)?)) + } + pub fn concat_iter(items: impl Iterator) -> Result { + let mut maps = Vec::new(); + for item in items { + if item.len() > 0 { + maps.extend(item.0.iter().cloned()); + } + } + Self::new(maps) + } pub fn identity(shapes: Vec, len: usize) -> Self { - Self(UniformConcat::new_unchecked(vec![UniformTesselation::identity(shapes, len)])) + Self(UniformConcat::new_unchecked(vec![ + UniformTesselation::identity(shapes, len), + ])) } - pub fn len(&self) -> Self { + pub fn len(&self) -> usize { self.0.len_in() } - pub fn dim(&self) -> Self { + pub fn dim(&self) -> usize { self.0.dim_in() } pub fn product(&self, other: &Self) -> Self { - unimplemented!{} + self * other } - pub fn concatenate(&self, other: &Self) -> Self { - unimplemented!{} + pub fn concat(&self, other: &Self) -> Result { + let maps = self.0.iter().chain(other.0.iter()).cloned().collect(); + Ok(Self(UniformConcat::new(maps)?)) } pub fn take(&self, indices: &[usize]) -> Self { - unimplemented!{} + assert!(indices.windows(2).all(|pair| pair[0] < pair[1])); + if let Some(last) = indices.last() { + assert!(*last < self.len()); + } + // TODO: Make sure that `UniformConcat` accepts an empty vector. + let maps = self.0.iter().scan((0, 0), |(start, offset), map| { + let stop = *start + indices[*start..].partition_point(|i| *i < *offset + map.len_in()); + let map_indices: Vec<_> = indices[*start..stop].iter().map(|i| *i - *offset).collect(); + *start = stop; + *offset += map.len_in(); + Some(map.take(&map_indices)) + }); + let result = Self(UniformConcat::new_unchecked(maps.collect())); + assert_eq!(result.len(), indices.len()); + result } pub fn children(&self) -> Self { - Self(UniformConcat::new_unchecked(self.0.iter().map(|item| item.children()).collect())) + let maps = self.0.iter().map(|item| item.children()).collect(); + Self(UniformConcat::new_unchecked(maps)) } - pub fn edges(&self) -> Result { - let edges = self.0.iter().map(|item| item.edges()).collect::>()?; - Self(UniformConcat::new_unchecked(edges)) + pub fn edges(&self) -> Result { + if self.dim_in() == 0 { + return Err(Error::DimensionZeroHasNoEdges); + } + let items: Vec<_> = self + .0 + .iter() + .flat_map(|item| item.edges_iter().unwrap()) + .collect(); + Ok(Self(UniformConcat::new(items)?)) } pub fn centroids(&self) -> Self { - unimplemented!{} + Self(UniformConcat::new_unchecked( + self.0.iter().map(|item| item.centroids()).collect(), + )) } pub fn vertices(&self) -> Self { - unimplemented!{} + unimplemented! {} } - pub fn apply_inplace(&self, mut index: usize, coords: &mut [f64], stride: usize) -> Option { + pub fn apply_inplace(&self, index: usize, coords: &mut [f64], stride: usize) -> Option { self.0.apply_inplace(index, coords, stride, 0) } - pub fn unapply_indices(&self, indices: &[T]) -> Vec { + pub fn unapply_indices(&self, indices: &[T]) -> Option> { self.0.unapply_indices(indices) } } -impl Deref for Tesselation { - type Target = OptionReorder>, Vec>; +//impl Deref for Tesselation { +// type Target = OptionReorder>, Vec>; +// +// fn deref(&self) -> Self::Target { +// self.0 +// } +//} - fn deref(&self) -> Self::Target { - self.0 - } +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $(-> $ret:ty)? + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; } - - - - - -enum Tesselation { - Uniform(UniformTesselation), - Concatenation(Vec), - Product(Vec), - Reordered(Box, Vec), +impl Map for Tesselation { + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} + dispatch! {fn is_index_map(&self) -> bool} } +impl RelativeTo for Tesselation { + type Output = as RelativeTo< + UniformConcat, + >>::Output; -impl RelativeTo for Tesselation { - fn relative_to(&self, target: &Tesselation) -> ... { - // convert product + fn relative_to(&self, target: &Self) -> Option { + self.0.relative_to(&target.0) } } +impl Mul for &UniformTesselation { + type Output = UniformTesselation; - - - - -struct UniformTesselation { - shapes: Vec - delta_dim: usize, - len_out: usize, - maps: Vec<(usize, Vec)>, - reorder: Option>, -} - -impl Tesselation { - pub fn new_identity(shapes: Vec, len: usize) -> Self { - Self { - shapes, - delta_dim: 0, - len_out: len, - maps: vec![(len, Vec::new()], - reorder: None, - } - } - pub fn product(&self, other: &Self) -> Self { - unimplemented!{} - } - pub fn concatenate(&self, other: &Self) -> Self { - unimplemented!{} - } - pub fn take(&self, indices: &[usize]) -> Self { - unimplemented!{} - } - pub fn children(&self) -> Self { - unimplemented!{} - } - pub fn edges(&self) -> Self { - unimplemented!{} - } - pub fn centroids(&self) -> Self { - unimplemented!{} - } - pub fn vertices(&self) -> Self { - unimplemented!{} - } - pub fn len(&self) -> usize { - self.maps.iter().map(|(n, _)| n).sum() - } - pub fn dim(&self) -> usize { - self.shapes.iter().map(|shape| shape.dim()).sum() - } - pub fn apply_inplace(&self, mut index: usize, coords: &mut [f64], stride: usize) -> Option { - for (len, map) in &self.maps { - if index < len { - return map.apply_inplace(index, coords, stride, 0); - } - index -= len; - } - None - } - pub fn unapply_indices(&self, indices: &[T]) -> Vec { - unimplemented!{} + fn mul(self, rhs: Self) -> UniformTesselation { + let offset = self.map.dim_in(); + let map = iter::once(Primitive::new_transpose( + rhs.map.len_out(), + self.map.len_out(), + )) + .chain(self.map.unbounded().iter().cloned()) + .chain(iter::once(Primitive::new_transpose( + self.map.len_in(), + rhs.map.len_out(), + ))) + .chain(rhs.map.unbounded().iter().map(|item| { + let mut item = item.clone(); + item.add_offset(offset); + item + })) + .collect(); + let map = WithBounds::new_unchecked( + map, + self.map.dim_in() + rhs.map.dim_in(), + self.map.len_in() * rhs.map.len_in(), + ); + let shapes = self + .shapes + .iter() + .chain(rhs.shapes.iter()) + .cloned() + .collect(); + UniformTesselation { shapes, map } } } -// struct ProductTesselation(Vec); +impl Mul for &Tesselation { + type Output = Tesselation; -// struct Relative + fn mul(self, rhs: Self) -> Tesselation { + let products = self + .0 + .iter() + .flat_map(|lhs| rhs.0.iter().map(move |rhs| lhs * rhs)) + .collect(); + Tesselation(UniformConcat::new_unchecked(products)) + } +} +#[cfg(test)] +mod tests { + use super::*; + use crate::simplex::Simplex::*; + use approx::assert_abs_diff_eq; + #[test] + fn children() { + let tess = Tesselation::identity(vec![Line], 1).children(); + assert_eq!(tess.len(), 2); + let tess = tess.children(); + assert_eq!(tess.len(), 4); + } + #[test] + fn product() { + let lhs = Tesselation::identity(vec![Line], 1).children(); + let rhs = Tesselation::identity(vec![Line], 1).edges().unwrap(); + let tess = &lhs * &rhs; + let centroids = tess.centroids(); + let stride = 2; + let mut work: Vec<_> = iter::repeat(-1.0).take(stride).collect(); + println!("tess: {tess:?}"); + assert_eq!(centroids.apply_inplace(0, &mut work, stride), Some(0)); + assert_abs_diff_eq!(work[..], [0.25, 1.0]); + assert_eq!(centroids.apply_inplace(1, &mut work, stride), Some(0)); + assert_abs_diff_eq!(work[..], [0.25, 0.0]); + assert_eq!(centroids.apply_inplace(2, &mut work, stride), Some(0)); + assert_abs_diff_eq!(work[..], [0.75, 1.0]); + assert_eq!(centroids.apply_inplace(3, &mut work, stride), Some(0)); + assert_abs_diff_eq!(work[..], [0.75, 0.0]); + } -//#[derive(Debug, Clone, PartialEq)] -//pub struct WithShape { -// map: M, -// shapes: Vec, -// dim_in: usize, -// delta_dim: usize, -// len_out: usize, -// len_in: usize, -//} -// -//impl WithShape { -// pub fn new(map: M, shapes: Vec, len_in: usize) -> Result { -// let dim_in: usize = shapes.iter().map(|simplex| simplex.dim()).sum(); -// if dim_in < map.dim_in() { -// Err(WithBoundsError::DimensionTooSmall) -// } else if len_in != 0 && len_in.checked_rem(map.mod_in()) != Some(0) { -// Err(WithBoundsError::LengthNotAMultipleOfRepetition) -// } else { -// Ok(Self::new_unchecked(map, shapes, len_in)) -// } -// } -// pub(crate) fn new_unchecked(map: M, shapes: Vec, len_in: usize) -> Self { -// let dim_in: usize = shapes.iter().map(|simplex| simplex.dim()).sum(); -// let delta_dim = map.delta_dim(); -// let len_out = if len_in == 0 { -// 0 -// } else { -// len_in / map.mod_in() * map.mod_out() -// }; -// Self { -// map, -// shapes, -// dim_in, -// delta_dim, -// len_out, -// len_in, -// } -// } -// pub fn get_unbounded(&self) -> &M { -// &self.map -// } -// pub fn into_unbounded(self) -> M { -// self.map -// } -// pub fn children(&self) -> Self { -// let mut map = self.map.clone(); -// let mut offset = 0; -// let mut len_in = self.len_in; -// for simplex in &self.shapes { -// let mut children = Elementary::new_children(*simplex); -// children.add_offset(offset); -// map.push_elementary(&children); -// len_in *= simplex.nchildren(); -// } -// Self::new_unchecked(map, self.shapes.clone(), len_in) -// } -// pub fn edges(&self) -> Self { -// let mut map = self.map.clone(); -// let mut offset = 0; -// let mut len_in = self.len_in; -// let mut shapes = Vec::new(); -// for simplex in &self.shapes { -// let mut edges = Elementary::new_edges(*simplex); -// edges.add_offset(offset); -// map.push_elementary(&edges); -// len_in *= simplex.nedges(); -// if let Some(edge_simplex) = simplex.edge_simplex() { -// shapes.push(edge_simplex); -// } -// } -// Self::new_unchecked(map, shapes, len_in) -// } -//} -// -//impl BoundedMap for WithShape { -// fn len_in(&self) -> usize { -// self.len_in -// } -// fn len_out(&self) -> usize { -// self.len_out -// } -// fn dim_in(&self) -> usize { -// self.dim_in -// } -// fn delta_dim(&self) -> usize { -// self.delta_dim -// } -// fn apply_inplace_unchecked( -// &self, -// index: usize, -// coordinates: &mut [f64], -// stride: usize, -// offset: usize, -// ) -> usize { -// self.map.apply_inplace(index, coordinates, stride, offset) -// } -// fn apply_index_unchecked(&self, index: usize) -> usize { -// self.map.apply_index(index) -// } -// fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { -// self.map.apply_indices_inplace(indices) -// } -// fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { -// self.map.unapply_indices(indices) -// } -// fn is_identity(&self) -> bool { -// self.map.is_identity() -// } -//} -// -//#[derive(Debug, Clone, PartialEq)] -//pub struct Tesselation(Concatenation>>); -// -//impl Tesselation { -// pub fn new_identity(shapes: Vec, len: usize) -> Self { -// Self(Concatenation::new(vec![WithShape::new_unchecked( -// vec![], -// shapes, -// len, -// )])) -// } -// pub fn iter(&self) -> impl Iterator>> { -// self.0.iter() -// } -// pub fn into_vec(self) -> Vec>> { -// self.0.into_vec() -// } -// pub fn take(&self, indices: &[usize]) -> Self { -// unimplemented! {} -// } -// pub fn children(&self) -> Result { -// unimplemented! {} -// } -// pub fn edges(&self) -> Result { -// unimplemented! {} -// } -// pub fn internal_edges_of_children(&self) -> Result { -// unimplemented! {} -// } -//} -// -//macro_rules! dispatch { -// ( -// $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( -// &$self:ident $(, $arg:ident: $ty:ty)* -// ) $(-> $ret:ty)? -// ) => { -// #[inline] -// $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { -// $self.0.$fn($($arg),*) -// } -// }; -// ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { -// #[inline] -// $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { -// $self.0.$fn($($arg),*) -// } -// }; -//} -// -//impl BoundedMap for Tesselation { -// dispatch! {fn len_out(&self) -> usize} -// dispatch! {fn len_in(&self) -> usize} -// dispatch! {fn dim_out(&self) -> usize} -// dispatch! {fn dim_in(&self) -> usize} -// dispatch! {fn delta_dim(&self) -> usize} -// dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} -// dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} -// dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} -// dispatch! {fn apply_index(&self, index: usize) -> Option} -// dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} -// dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} -// dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} -// dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} -// dispatch! {fn is_identity(&self) -> bool} -//} + #[test] + fn take() { + let lhs = Tesselation::identity(vec![Line], 1).children(); + let rhs = Tesselation::identity(vec![Line], 1).edges().unwrap(); + let levels: Vec<_> = iter::successors(Some(&lhs * &rhs), |level| Some(level.children())) + .take(3) + .collect(); + let hierarch = levels[1].take(&[0, 1, 2]); + } +} diff --git a/src/topology.rs b/src/topology.rs index a2c9a168c..9647c483e 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -1,13 +1,11 @@ -use crate::elementary::{Elementary, PushElementary as _}; -use crate::ops::{Concatenation, WithBounds, WithBoundsError}; use crate::relative::RelativeTo as _; use crate::simplex::Simplex; -use crate::{AddOffset, BoundedMap, UnapplyIndicesData, UnboundedMap}; +use crate::tesselation::Tesselation; +use crate::{AddOffset, Map, UnapplyIndicesData}; use std::iter; use std::ops::{Deref, DerefMut}; use std::rc::Rc; - -type Tesselation = UniformConcat>>; +use std::ops::Mul; #[derive(Debug, Clone, PartialEq)] pub struct Root { @@ -24,10 +22,10 @@ impl Root { pub trait TopologyCore: std::fmt::Debug + Clone + Into { fn tesselation(&self) -> &Tesselation; fn dim(&self) -> usize { - self.tesselation().dim_in() + self.tesselation().dim() } fn ntiles(&self) -> usize { - self.tesselation().len_in() + self.tesselation().len() } fn refined(&self) -> Topology; fn map_itiles_to_refined(&self, itiles: &[usize]) -> Vec { @@ -40,11 +38,15 @@ pub trait TopologyCore: std::fmt::Debug + Clone + Into { fn take(&self, itiles: &[usize]) -> Topology { let mut itiles: Vec = itiles.to_vec(); itiles.sort_by_key(|&index| index); - Take::new(self.clone().into(), itiles) + Take::new(self.clone().into(), itiles).into() } fn refined_by(&self, itiles: &[usize]) -> Topology { Hierarchical::new(self.clone().into(), vec![(0..self.ntiles()).collect()]) .refined_by(itiles) + .into() + } + fn centroids(&self) -> Topology { + Point::new(self.tesselation().centroids()).into() } } @@ -58,6 +60,15 @@ pub enum Topology { Hierarchical(Rc), } +impl Topology { + fn new_line(len: usize) -> Self { + Line::from_len(len).into() + } + fn disjoin_union(self, other: Self) -> Self { + DisjointUnion::new(self, other).into() + } +} + macro_rules! dispatch { ( $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( @@ -116,9 +127,22 @@ macro_rules! impl_from_topo { impl_from_topo! {Point, Line, Product, DisjointUnion, Take, Hierarchical} +impl Mul for Topology { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + Product::new(self, rhs).into() + } +} + #[derive(Debug, Clone, PartialEq)] -pub struct Point { - tesselation: Tesselation, +pub struct Point(Tesselation); + +impl Point { + pub fn new(tesselation: Tesselation) -> Self { + assert_eq!(tesselation.dim(), 0); + Self(tesselation) + } } impl TopologyCore for Point { @@ -126,7 +150,7 @@ impl TopologyCore for Point { 0 } fn tesselation(&self) -> &Tesselation { - &self.tesselation + &self.0 } fn refined(&self) -> Topology { self.clone().into() @@ -137,19 +161,15 @@ impl TopologyCore for Point { } #[derive(Debug, Clone, PartialEq)] -pub struct Line { - tesselation: Tesselation, -} +pub struct Line(Tesselation); impl Line { - pub fn new(tesselation: Tesselation) -> Topology { - assert_eq!(tesselation.dim_in(), 1); - Self { tesselation }.into() + pub fn new(tesselation: Tesselation) -> Self { + assert_eq!(tesselation.dim(), 1); + Self(tesselation) } - pub fn from_len(len: usize) -> Topology { - Self::new(Concatenation::new(vec![ - WithBounds::new(vec![], 1, len).unwrap() - ])) + pub fn from_len(len: usize) -> Self { + Self(Tesselation::identity(vec![Simplex::Line], len)) } } @@ -158,19 +178,13 @@ impl TopologyCore for Line { 1 } fn tesselation(&self) -> &Tesselation { - &self.tesselation + &self.0 } fn refined(&self) -> Topology { - let mut tesselation = self.tesselation.clone(); - tesselation.push_elementary(&Elementary::new_children(Simplex::Line)); - Self { tesselation }.into() + Self(self.tesselation().children()).into() } fn boundary(&self) -> Topology { - let mut tesselation = self.tesselation.clone(); - tesselation.push_elementary(&Elementary::new_edges(Simplex::Line)); - let n = tesselation.len_in(); - tesselation.push_elementary(&Elementary::new_take(vec![1, n - 2], n)); - Point { tesselation }.into() + Point(self.tesselation().edges().unwrap().take(&[1, 2 * self.ntiles() - 2])).into() } } @@ -181,20 +195,13 @@ pub struct DisjointUnion { } impl DisjointUnion { - pub fn new(topo0: Topology, topo1: Topology) -> Topology { - let tesselation = Concatenation::new( - topo0 - .tesselation() - .iter() - .chain(topo1.tesselation().iter()) - .cloned() - .collect(), - ); + pub fn new(topo0: Topology, topo1: Topology) -> Self { + // TODO: assert common roots + let tesselation = topo0.tesselation().concat(topo1.tesselation()).unwrap(); Self { topos: [topo0, topo1], tesselation, } - .into() } } @@ -203,10 +210,10 @@ impl TopologyCore for DisjointUnion { &self.tesselation } fn refined(&self) -> Topology { - Self::new(self.topos[0].refined(), self.topos[1].refined()) + Self::new(self.topos[0].refined(), self.topos[1].refined()).into() } fn boundary(&self) -> Topology { - Self::new(self.topos[0].boundary(), self.topos[1].boundary()) + Self::new(self.topos[0].boundary(), self.topos[1].boundary()).into() } } @@ -217,34 +224,13 @@ pub struct Product { } impl Product { - pub fn new(topo0: Topology, topo1: Topology) -> Topology { - let map0 = topo0.tesselation(); - let map1 = topo1.tesselation(); - let dim_out = map0.dim_out() + map1.dim_out(); - let len_out = map0.len_out() * map1.len_out(); - let mut parts = Vec::new(); - for part0 in topo0.tesselation().iter() { - for part1 in topo1.tesselation().iter() { - let part0 = part0.clone(); - let mut part1 = part1.clone(); - let trans1 = Elementary::new_transpose(part1.len_out(), part0.len_out()); - let trans2 = Elementary::new_transpose(part0.len_in(), part1.len_out()); - part1.add_offset(part0.dim_in()); - let part: Vec = iter::once(trans1) - .chain(part0.into_unbounded()) - .chain(iter::once(trans2)) - .chain(part1.into_unbounded()) - .collect(); - parts.push(WithBounds::new(part, dim_out, len_out).unwrap()); - } - } - let tesselation = Concatenation::new(parts); + pub fn new(topo0: Topology, topo1: Topology) -> Self { + let tesselation = topo0.tesselation() * topo1.tesselation(); // TODO: assert no common roots Self { topos: [topo0, topo1], tesselation, } - .into() } } @@ -253,13 +239,13 @@ impl TopologyCore for Product { &self.tesselation } fn refined(&self) -> Topology { - Self::new(self.topos[0].refined(), self.topos[1].refined()) + Self::new(self.topos[0].refined(), self.topos[1].refined()).into() } fn boundary(&self) -> Topology { DisjointUnion::new( - Product::new(self.topos[0].clone(), self.topos[1].boundary()), - Product::new(self.topos[0].boundary(), self.topos[1].clone()), - ) + Product::new(self.topos[0].clone(), self.topos[1].boundary()).into(), + Product::new(self.topos[0].boundary(), self.topos[1].clone()).into(), + ).into() } } @@ -271,16 +257,14 @@ pub struct Take { } impl Take { - pub fn new(topo: Topology, itiles: Vec) -> Topology { + pub fn new(topo: Topology, itiles: Vec) -> Self { // TODO: requires sorted itiles? - let mut tesselation = topo.tesselation().clone(); - tesselation.push_elementary(&Elementary::new_take(itiles.clone(), tesselation.len_in())); + let tesselation = topo.tesselation().take(&itiles); Self { topo, itiles, tesselation, } - .into() } } @@ -295,7 +279,7 @@ impl TopologyCore for Take { .unapply_indices_from(self.topo.tesselation(), &self.itiles) .unwrap(); itiles.sort_by_key(|&index| index); - Take::new(refined, itiles) + Take::new(refined, itiles).into() } fn boundary(&self) -> Topology { unimplemented! {} @@ -314,28 +298,19 @@ fn refine_iter(base: Topology) -> impl Iterator { } impl Hierarchical { - pub fn new(base: Topology, itiles: Vec>) -> Topology { - let tesselation = Concatenation::new( + pub fn new(base: Topology, itiles: Vec>) -> Self { + let tesselation = Tesselation::concat_iter( itiles .iter() .zip(refine_iter(base.clone())) - .filter(|(itiles, _)| !itiles.is_empty()) - .flat_map(|(itiles, level)| { - let mut tesselation = level.tesselation().clone(); - tesselation.push_elementary(&Elementary::new_take( - itiles.clone(), - tesselation.len_in(), - )); - tesselation.into_vec() - }) - .collect(), - ); + .map(|(itiles, level)| level.tesselation().take(itiles)) + ).unwrap(); + assert_eq!(itiles.iter().map(|item| item.len()).sum::(), tesselation.len()); Self { base, itiles, tesselation, } - .into() } fn levels(&self) -> impl Iterator { refine_iter(self.base.clone()) @@ -354,7 +329,7 @@ impl TopologyCore for Hierarchical { .itiles_levels() .map(|(itiles, level)| level.map_itiles_to_refined(itiles)) .collect(); - Hierarchical::new(self.base.refined(), itiles) + Hierarchical::new(self.base.refined(), itiles).into() } fn refined_by(&self, itiles: &[usize]) -> Topology { let mut global_itiles = itiles.to_vec(); @@ -380,7 +355,7 @@ impl TopologyCore for Hierarchical { if !queue.is_empty() { refined_itiles.push(queue); } - Hierarchical::new(self.base.clone(), refined_itiles) + Hierarchical::new(self.base.clone(), refined_itiles).into() } fn boundary(&self) -> Topology { let base_boundary = self.base.boundary(); @@ -396,7 +371,7 @@ impl TopologyCore for Hierarchical { itiles }) .collect(); - Hierarchical::new(base_boundary, itiles) + Hierarchical::new(base_boundary, itiles).into() } } @@ -427,7 +402,7 @@ mod tests { for (i, desired) in desired.into_iter().enumerate() { println!("i = {i}"); let mut actual = centroid_in.clone(); - let iroot = tesselation.apply_inplace(i, &mut actual, dim_out, 0).unwrap(); + let iroot = tesselation.apply_inplace(i, &mut actual, dim_out).unwrap(); geom(iroot, &mut actual); assert_abs_diff_eq!(actual[..], desired[..]); } @@ -451,13 +426,13 @@ mod tests { #[test] fn test2() { - let x = Line::from_len(2); - let y = Line::from_len(2); + let x = Topology::new_line(2); + let y = Topology::new_line(2); let geom = |i: usize, c: &mut [f64]| { c[0] += (i / 2) as f64; c[1] += (i % 2) as f64; }; - let xy = Product::new(x.clone(), y.clone()); + let xy: Topology = x.clone() * y.clone(); assert_centroids!( &xy, geom, @@ -484,13 +459,13 @@ mod tests { #[test] fn hierarchical() { - let x = Line::from_len(2); - let y = Line::from_len(2); + let x = Topology::new_line(2); + let y = Topology::new_line(2); let geom = |i: usize, c: &mut [f64]| { c[0] += (i / 2) as f64; c[1] += (i % 2) as f64; }; - let xy0 = Product::new(x, y); + let xy0 = x * y; assert_centroids!( &xy0, geom, diff --git a/src/util.rs b/src/util.rs index dd7098177..40c339643 100644 --- a/src/util.rs +++ b/src/util.rs @@ -25,6 +25,37 @@ impl Iterator for ReplaceNth { } } +pub trait SkipNthIter: Iterator + Sized { + /// Skips the nth item of the iterator. + fn skip_nth(self, index: usize) -> SkipNth; +} + +impl SkipNthIter for Iter { + fn skip_nth(self, index: usize) -> SkipNth { + SkipNth(self, 0, index) + } +} + +pub struct SkipNth(Iter, usize, usize); + +impl Iterator for SkipNth { + type Item = Iter::Item; + + fn next(&mut self) -> Option { + if let Some(value) = self.0.next() { + let next = if self.1 == self.2 { + self.0.next() + } else { + Some(value) + }; + self.1 += 1; + next + } else { + None + } + } +} + #[cfg(test)] mod tests { use super::*; From 0b0e6f6eaf15091865192dfc6c8a7e44a2a821cc Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Tue, 5 Jul 2022 14:50:45 +0200 Subject: [PATCH 34/45] WIP --- .gitignore | 1 + Cargo.toml | 12 +- Makefile | 25 +++ pyproject.toml | 7 + setup.py | 50 ------ src/lib.rs | 307 ++++++++++++++++++----------------- src/map/mod.rs | 181 +++++++++++++++++++++ src/{ => map}/ops.rs | 4 +- src/{ => map}/primitive.rs | 24 +-- src/{ => map}/relative.rs | 22 +-- src/{ => map}/tesselation.rs | 33 +++- 11 files changed, 430 insertions(+), 236 deletions(-) create mode 100644 Makefile create mode 100644 pyproject.toml delete mode 100644 setup.py create mode 100644 src/map/mod.rs rename src/{ => map}/ops.rs (99%) rename src/{ => map}/primitive.rs (98%) rename src/{ => map}/relative.rs (97%) rename src/{ => map}/tesselation.rs (93%) diff --git a/.gitignore b/.gitignore index ecef57f70..ed544328f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ __pycache__/ /.eggs/ /target Cargo.lock +/nutils/_rust.so diff --git a/Cargo.toml b/Cargo.toml index b50a80617..cde71ec79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,18 @@ [package] -name = "nutils-test" +name = "nutils" version = "0.1.0" +authors = ["Evalf "] edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "nutils" +crate-type = ["cdylib"] + +[package.metadata.maturin] +name = "nutils._rust" [dependencies] approx = "0.5" num = "0.4" +pyo3 = { version = "0.16", features = ["extension-module", "abi3-py37"] } +numpy = "0.16" diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..792d7efdb --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +develop: build + cp --reflink=auto target/debug/libnutils.so nutils/_rust.so + +develop-release: build-release + cp --reflink=auto target/release/libnutils.so nutils/_rust.so + +build: + cargo build + +build-release: + cargo build --release + +bench: + cargo +nightly bench --features bench + +bench-python-compare: + python3 -m pytest benches/ --benchmark-compare --benchmark-group-by=name + +test-rust: + cargo test + +docs-python: develop-release + python3 -m sphinx -n -W --keep-going -E -D html_theme=sphinx_rtd_theme docs build/sphinx/html + +.PHONY: build diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..ca2cfb811 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "nutils" +dependencies = ["numpy>=1.17", "treelog>=1.0b5", "stringly"] + +[build-system] +requires = ["maturin>=0.12,<0.13"] +build-backend = "maturin" diff --git a/setup.py b/setup.py deleted file mode 100644 index af6ecdaf6..000000000 --- a/setup.py +++ /dev/null @@ -1,50 +0,0 @@ -import re -import os -from setuptools import setup - -long_description = """ -Nutils is a Free and Open Source Python programming library for Finite Element -Method computations, developed by `Evalf Computing `_ and -distributed under the permissive MIT license. Key features are a readable, math -centric syntax, an object oriented design, strict separation of topology and -geometry, and high level function manipulations with support for automatic -differentiation. - -Nutils provides the tools required to construct a typical simulation workflow -in just a few lines of Python code, while at the same time leaving full -flexibility to build novel workflows or interact with third party tools. With -native support for Isogeometric Analysis (IGA), the Finite Cell method (FCM), -multi-physics, mixed methods, and hierarchical refinement, Nutils is at the -forefront of numerical discretization science. Efficient under-the-hood -vectorization and built-in parallellisation provide for an effortless -transition from academic research projects to full scale, real world -applications. -""" - -with open(os.path.join('nutils', '__init__.py')) as f: - version = next(filter(None, map(re.compile("^version = '([a-zA-Z0-9.]+)'$").match, f))).group(1) - -setup( - name='nutils', - version=version, - description='Numerical Utilities for Finite Element Analysis', - author='Evalf', - author_email='info@nutils.org', - url='http://nutils.org', - download_url='https://github.com/nutils/nutils/releases', - packages=['nutils', 'nutils.matrix'], - long_description=long_description, - license='MIT', - python_requires='>=3.7', - install_requires=['numpy>=1.17', 'treelog>=1.0b5', 'stringly'], - extras_require=dict( - docs=['Sphinx>=1.8'], - matrix_scipy=['scipy>=0.13'], - matrix_mkl=['mkl'], - export_mpl=['matplotlib>=1.3', 'pillow>2.6'], - import_gmsh=['meshio'], - ), - command_options=dict( - test=dict(test_loader=('setup.py', 'unittest:TestLoader')), - ), -) diff --git a/src/lib.rs b/src/lib.rs index 297882bd6..bea6b6a30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,176 +1,179 @@ pub mod finite_f64; -pub mod ops; -pub mod primitive; -pub mod relative; +pub mod map; pub mod simplex; -pub mod tesselation; -pub mod topology; mod util; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Error { - Empty, - DimensionMismatch, - LengthMismatch, - DimensionZeroHasNoEdges, -} - -impl std::error::Error for Error {} +use map::tesselation::Tesselation; +use map::Map; +use numpy::{IntoPyArray, IxDyn, PyArray, PyArrayDyn, PyReadonlyArrayDyn}; +use pyo3::exceptions::{PyIndexError, PyValueError}; +use pyo3::prelude::*; +use simplex::Simplex; +use std::iter; -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::Empty => write!(f, "The input array is empty."), - Self::DimensionMismatch => write!(f, "The dimensions of the maps differ."), - Self::LengthMismatch => write!(f, "The lengths of the maps differ."), - Self::DimensionZeroHasNoEdges => write!(f, "Dimension zero has no edges."), - } +impl From for PyErr { + fn from(err: map::Error) -> PyErr { + PyValueError::new_err(err.to_string()) } } -/// An interface for an index and coordinate map. -pub trait Map { - /// Returns the exclusive upper bound of the indices in the codomain. - fn len_out(&self) -> usize; - /// Returns the exclusive upper bound of the indices of the domain. - fn len_in(&self) -> usize; - /// Returns the dimension of the coordinates of the codimain. - fn dim_out(&self) -> usize { - self.dim_in() + self.delta_dim() - } - /// Returns the dimension of the coordinates of the dimain. - fn dim_in(&self) -> usize; - /// Returns the dimension difference of the coordinates in the codomain and the domain. - fn delta_dim(&self) -> usize; - /// Apply the given index and coordinate, the latter in-place, without - /// checking whether the index is inside the domain and the coordinates - /// have at least dimension [`Self::dim_out()`]. - fn apply_inplace_unchecked( - &self, - index: usize, - coords: &mut [f64], - stride: usize, - offset: usize, - ) -> usize; - /// Applies the given index and coordinate, the latter in-place. The - /// coordinates must have a dimension not smaller than - /// [`Self::dim_out()`]. Returns `None` if the index is outside the domain. - fn apply_inplace( - &self, - index: usize, - coords: &mut [f64], - stride: usize, - offset: usize, - ) -> Option { - if index < self.len_in() && offset + self.dim_out() <= stride { - Some(self.apply_inplace_unchecked(index, coords, stride, offset)) - } else { - None +#[pymodule] +#[allow(non_snake_case)] +fn _rust(py: Python, m: &PyModule) -> PyResult<()> { + let sys_modules = py.import("sys")?.getattr("modules")?; + + #[pyclass(name = "Simplex", module = "nutils._rust")] + #[derive(Debug, Clone)] + struct PySimplex(Simplex); + + #[pymethods] + impl PySimplex { + #[classattr] + pub fn line() -> Self { + Simplex::Line.into() } - } - /// Applies the given index without checking that the index is inside the - /// domain. - fn apply_index_unchecked(&self, index: usize) -> usize; - /// Apply the given index. Returns `None` if the index is outside the - /// domain, - fn apply_index(&self, index: usize) -> Option { - if index < self.len_in() { - Some(self.apply_index_unchecked(index)) - } else { - None + #[classattr] + pub fn triangle() -> Self { + Simplex::Triangle.into() + } + #[getter] + pub fn dim(&self) -> usize { + self.0.dim() + } + #[getter] + pub fn edge_dim(&self) -> usize { + self.0.edge_dim() + } + #[getter] + pub fn edge_simplex(&self) -> Option { + self.0.edge_simplex().map(|simplex| simplex.into()) + } + #[getter] + pub fn nchildren(&self) -> usize { + self.0.nchildren() + } + #[getter] + pub fn nedges(&self) -> usize { + self.0.nedges() } } - /// Applies the given indices in-place without checking that the indices are - /// inside the domain. - fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { - for index in indices.iter_mut() { - *index = self.apply_index_unchecked(*index); + + impl From for PySimplex { + fn from(simplex: Simplex) -> PySimplex { + PySimplex(simplex) } } - /// Applies the given indices in-place. Returns `None` if any of the - /// indices is outside the domain. - fn apply_indices(&self, indices: &[usize]) -> Option> { - if indices.iter().all(|index| *index < self.len_in()) { - let mut indices = indices.to_vec(); - self.apply_indices_inplace_unchecked(&mut indices); - Some(indices) - } else { - None + + impl From for Simplex { + fn from(pysimplex: PySimplex) -> Simplex { + pysimplex.0 } } - fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec; - fn unapply_indices(&self, indices: &[T]) -> Option> { - if indices.iter().all(|index| index.get() < self.len_out()) { - Some(self.unapply_indices_unchecked(indices)) - } else { - None + + impl From<&PySimplex> for Simplex { + fn from(pysimplex: &PySimplex) -> Simplex { + pysimplex.0 } } - /// Returns true if this map is the identity map. - fn is_identity(&self) -> bool; - /// Returns true if this map returns coordinates unaltered. - fn is_index_map(&self) -> bool; -} -pub trait AddOffset { - fn add_offset(&mut self, offset: usize); -} + m.add_class::()?; -pub trait UnapplyIndicesData: Clone + std::fmt::Debug { - fn get(&self) -> usize; - fn set(&self, index: usize) -> Self; -} + #[pyclass(name = "Tesselation", module = "nutils._rust")] + #[derive(Debug, Clone)] + struct PyTesselation(Tesselation); -impl UnapplyIndicesData for usize { - #[inline] - fn get(&self) -> usize { - *self - } - #[inline] - fn set(&self, index: usize) -> Self { - index + #[pymethods] + impl PyTesselation { + #[staticmethod] + pub fn identity(shapes: Vec, len: usize) -> Self { + let shapes = shapes.iter().map(|shape| shape.into()).collect(); + Tesselation::identity(shapes, len).into() + } + pub fn __len__(&self) -> usize { + self.0.len() + } + #[getter] + pub fn dim(&self) -> usize { + self.0.len() + } + pub fn __mul__(&self, rhs: &PyTesselation) -> Self { + Self(&self.0 * &rhs.0) + } + pub fn concat(&self, other: &PyTesselation) -> PyResult { + Ok(Self(self.0.concat(&other.0)?)) + } + pub fn take(&self, indices: Vec) -> Self { + Self(self.0.take(&indices)) + } + #[getter] + pub fn children(&self) -> Self { + Self(self.0.children()) + } + #[getter] + pub fn edges(&self) -> PyResult { + Ok(Self(self.0.edges()?)) + } + #[getter] + pub fn centroids(&self) -> Self { + Self(self.0.centroids()) + } + #[getter] + pub fn vertices(&self) -> Self { + Self(self.0.vertices()) + } + pub fn apply_index(&self, index: usize) -> PyResult { + self.0 + .apply_index(index) + .ok_or(PyIndexError::new_err("index out of range")) + } + pub fn apply<'py>( + &self, + py: Python<'py>, + index: usize, + coords: PyReadonlyArrayDyn, + ) -> PyResult<(usize, &'py PyArrayDyn)> { + if coords.ndim() == 0 { + return Err(PyValueError::new_err( + "the `coords` argument must have at least one dimension", + )); + } + if coords.shape()[coords.ndim() - 1] != self.0.dim() { + return Err(PyValueError::new_err(format!( + "the last axis of the `coords` argument should have dimension {}", + self.0.dim() + ))); + } + let mut result: Vec = coords + .as_array() + .rows() + .into_iter() + .flat_map(|row| { + row.into_iter() + .cloned() + .chain(iter::repeat(0.0).take(self.0.delta_dim())) + }) + .collect(); + let index = self.0.apply_inplace(index, &mut result, self.0.dim_out())?; + let result = PyArray::from_vec(py, result); + let shape: Vec = coords + .shape() + .iter() + .take(coords.ndim() - 1) + .cloned() + .chain(iter::once(self.0.dim_out())) + .collect(); + let result = result.reshape(&shape[..])?; + Ok((index, result)) + } } -} -#[macro_export] -macro_rules! assert_map_apply { - ($item:expr, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ - use std::borrow::Borrow; - let item = $item.borrow(); - let incoords = $incoords; - let outcoords = $outcoords; - assert_eq!(incoords.len(), outcoords.len(), "incoords outcoords"); - let stride; - let mut work: Vec<_>; - if incoords.len() == 0 { - stride = item.dim_out(); - work = Vec::with_capacity(0); - } else { - stride = outcoords[0].len(); - work = iter::repeat(-1.0).take(outcoords.len() * stride).collect(); - for (work, incoord) in iter::zip(work.chunks_mut(stride), incoords.iter()) { - work[..incoord.len()].copy_from_slice(incoord); - } + impl From for PyTesselation { + fn from(tesselation: Tesselation) -> PyTesselation { + PyTesselation(tesselation) } - assert_eq!( - item.apply_inplace($inidx, &mut work, stride, 0), - Some($outidx), - "apply_inplace", - ); - assert_eq!(item.apply_index($inidx), Some($outidx), "apply_index"); - for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { - assert_abs_diff_eq!(actual[..], desired[..]); - } - }}; - ($item:expr, $inidx:expr, $outidx:expr) => {{ - use std::borrow::Borrow; - let item = $item.borrow(); - let mut work = Vec::with_capacity(0); - assert_eq!( - item.apply_inplace($inidx, &mut work, item.dim_out(), 0) - .unwrap(), - $outidx - ); - assert_eq!(item.apply_index($inidx), Some($outidx)); - }}; + } + + m.add_class::()?; + + Ok(()) } diff --git a/src/map/mod.rs b/src/map/mod.rs new file mode 100644 index 000000000..074212d1a --- /dev/null +++ b/src/map/mod.rs @@ -0,0 +1,181 @@ +pub mod ops; +pub mod primitive; +pub mod relative; +pub mod tesselation; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + Empty, + DimensionMismatch, + LengthMismatch, + DimensionZeroHasNoEdges, + StrideSmallerThanOutputDimension, + IndexOutOfRange, +} + +impl std::error::Error for Error {} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Empty => write!(f, "The input array is empty."), + Self::DimensionMismatch => write!(f, "The dimensions of the maps differ."), + Self::LengthMismatch => write!(f, "The lengths of the maps differ."), + Self::DimensionZeroHasNoEdges => write!(f, "Dimension zero has no edges."), + Self::StrideSmallerThanOutputDimension => write!( + f, + "The stride of the `coords` argument is smaller than the output dimension." + ), + Self::IndexOutOfRange => write!(f, "The index is out of range."), + } + } +} + +/// An interface for an index and coordinate map. +pub trait Map { + /// Returns the exclusive upper bound of the indices in the codomain. + fn len_out(&self) -> usize; + /// Returns the exclusive upper bound of the indices of the domain. + fn len_in(&self) -> usize; + /// Returns the dimension of the coordinates of the codimain. + fn dim_out(&self) -> usize { + self.dim_in() + self.delta_dim() + } + /// Returns the dimension of the coordinates of the dimain. + fn dim_in(&self) -> usize; + /// Returns the dimension difference of the coordinates in the codomain and the domain. + fn delta_dim(&self) -> usize; + /// Apply the given index and coordinate, the latter in-place, without + /// checking whether the index is inside the domain and the coordinates + /// have at least dimension [`Self::dim_out()`]. + fn apply_inplace_unchecked( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> usize; + /// Applies the given index and coordinate, the latter in-place. The + /// coordinates must have a dimension not smaller than + /// [`Self::dim_out()`]. Returns `None` if the index is outside the domain. + fn apply_inplace( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + offset: usize, + ) -> Result { + if index >= self.len_in() { + Err(Error::IndexOutOfRange) + } else if offset + self.dim_out() > stride { + Err(Error::StrideSmallerThanOutputDimension) + } else { + Ok(self.apply_inplace_unchecked(index, coords, stride, offset)) + } + } + /// Applies the given index without checking that the index is inside the + /// domain. + fn apply_index_unchecked(&self, index: usize) -> usize; + /// Apply the given index. Returns `None` if the index is outside the + /// domain, + fn apply_index(&self, index: usize) -> Option { + if index < self.len_in() { + Some(self.apply_index_unchecked(index)) + } else { + None + } + } + /// Applies the given indices in-place without checking that the indices are + /// inside the domain. + fn apply_indices_inplace_unchecked(&self, indices: &mut [usize]) { + for index in indices.iter_mut() { + *index = self.apply_index_unchecked(*index); + } + } + /// Applies the given indices in-place. Returns `None` if any of the + /// indices is outside the domain. + fn apply_indices(&self, indices: &[usize]) -> Option> { + if indices.iter().all(|index| *index < self.len_in()) { + let mut indices = indices.to_vec(); + self.apply_indices_inplace_unchecked(&mut indices); + Some(indices) + } else { + None + } + } + fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec; + fn unapply_indices(&self, indices: &[T]) -> Option> { + if indices.iter().all(|index| index.get() < self.len_out()) { + Some(self.unapply_indices_unchecked(indices)) + } else { + None + } + } + /// Returns true if this map is the identity map. + fn is_identity(&self) -> bool; + /// Returns true if this map returns coordinates unaltered. + fn is_index_map(&self) -> bool; +} + +pub trait AddOffset { + fn add_offset(&mut self, offset: usize); +} + +pub trait UnapplyIndicesData: Clone + std::fmt::Debug { + fn get(&self) -> usize; + fn set(&self, index: usize) -> Self; +} + +impl UnapplyIndicesData for usize { + #[inline] + fn get(&self) -> usize { + *self + } + #[inline] + fn set(&self, index: usize) -> Self { + index + } +} + +#[macro_export] +macro_rules! assert_map_apply { + ($item:expr, $inidx:expr, $incoords:expr, $outidx:expr, $outcoords:expr) => {{ + use std::borrow::Borrow; + let item = $item.borrow(); + let incoords = $incoords; + let outcoords = $outcoords; + assert_eq!(incoords.len(), outcoords.len(), "incoords outcoords"); + let stride; + let mut work: Vec<_>; + if incoords.len() == 0 { + stride = item.dim_out(); + work = Vec::with_capacity(0); + } else { + stride = outcoords[0].len(); + work = iter::repeat(-1.0).take(outcoords.len() * stride).collect(); + for (work, incoord) in iter::zip(work.chunks_mut(stride), incoords.iter()) { + work[..incoord.len()].copy_from_slice(incoord); + } + } + assert_eq!( + item.apply_inplace($inidx, &mut work, stride, 0), + Some($outidx), + "apply_inplace", + ); + assert_eq!(item.apply_index($inidx), Some($outidx), "apply_index"); + for (actual, desired) in iter::zip(work.chunks(stride), outcoords.iter()) { + assert_abs_diff_eq!(actual[..], desired[..]); + } + }}; + ($item:expr, $inidx:expr, $outidx:expr) => {{ + use std::borrow::Borrow; + let item = $item.borrow(); + let mut work = Vec::with_capacity(0); + assert_eq!( + item.apply_inplace($inidx, &mut work, item.dim_out(), 0) + .unwrap(), + $outidx + ); + assert_eq!(item.apply_index($inidx), Some($outidx)); + }}; +} diff --git a/src/ops.rs b/src/map/ops.rs similarity index 99% rename from src/ops.rs rename to src/map/ops.rs index 726b501b0..d5bc1c5a5 100644 --- a/src/ops.rs +++ b/src/map/ops.rs @@ -1,4 +1,4 @@ -use crate::{Error, Map, UnapplyIndicesData}; +use super::{Error, Map, UnapplyIndicesData}; use num::Integer as _; use std::ops::Deref; @@ -705,7 +705,7 @@ where coords: &mut [f64], stride: usize, offset: usize, - ) -> Option} + ) -> Result} dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} dispatch! {fn apply_index(&self, index: usize) -> Option} dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} diff --git a/src/primitive.rs b/src/map/primitive.rs similarity index 98% rename from src/primitive.rs rename to src/map/primitive.rs index bda90ec40..c2c1cb030 100644 --- a/src/primitive.rs +++ b/src/map/primitive.rs @@ -1,9 +1,9 @@ +use super::{AddOffset, Error, Map, UnapplyIndicesData}; use crate::finite_f64::FiniteF64; use crate::simplex::Simplex; -use crate::{AddOffset, Error, Map, UnapplyIndicesData}; use num::Integer as _; use std::ops::{Deref, DerefMut}; -use std::rc::Rc; +use std::sync::Arc; /// An interface for an unbounded coordinate and index map. pub trait UnboundedMap { @@ -237,13 +237,13 @@ impl AddOffset for Transpose { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Take { - indices: Rc<[usize]>, + indices: Arc<[usize]>, nindices: usize, len: usize, } impl Take { - pub fn new(indices: impl Into>, len: usize) -> Self { + pub fn new(indices: impl Into>, len: usize) -> Self { // TODO: return err if indices.is_empty() let indices = indices.into(); assert!(!indices.is_empty()); @@ -254,7 +254,7 @@ impl Take { len, } } - pub fn get_indices(&self) -> Rc<[usize]> { + pub fn get_indices(&self) -> Arc<[usize]> { self.indices.clone() } } @@ -468,14 +468,14 @@ impl From for Edges { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct UniformPoints { - points: Rc<[FiniteF64]>, + points: Arc<[FiniteF64]>, npoints: usize, point_dim: usize, } impl UniformPoints { - pub fn new(points: impl Into>, point_dim: usize) -> Self { - let points: Rc<[FiniteF64]> = unsafe { std::mem::transmute(points.into()) }; + pub fn new(points: impl Into>, point_dim: usize) -> Self { + let points: Arc<[FiniteF64]> = unsafe { std::mem::transmute(points.into()) }; assert_eq!(points.len() % point_dim, 0); assert_ne!(point_dim, 0); let npoints = points.len() / point_dim; @@ -545,7 +545,7 @@ impl Primitive { Transpose::new(len1, len2).into() } #[inline] - pub fn new_take(indices: impl Into>, len: usize) -> Self { + pub fn new_take(indices: impl Into>, len: usize) -> Self { Take::new(indices, len).into() } #[inline] @@ -561,7 +561,7 @@ impl Primitive { Edges::new(simplex).into() } #[inline] - pub fn new_uniform_points(points: impl Into>, point_dim: usize) -> Self { + pub fn new_uniform_points(points: impl Into>, point_dim: usize) -> Self { UniformPoints::new(points, point_dim).into() } #[inline] @@ -1119,14 +1119,14 @@ where #[macro_export] macro_rules! prim_comp { (Point*$len_out:literal $($tail:tt)*) => {{ - use $crate::primitive::{Primitive, WithBounds}; + use $crate::map::primitive::{Primitive, WithBounds}; #[allow(unused_mut)] let mut comp: Vec = Vec::new(); $crate::prim_comp!{@adv comp, Point; $($tail)*} WithBounds::from_output(comp, 0, $len_out).unwrap() }}; ($simplex:tt*$len_out:literal $($tail:tt)*) => {{ - use $crate::primitive::{Primitive, WithBounds}; + use $crate::map::primitive::{Primitive, WithBounds}; #[allow(unused_mut)] let mut comp: Vec = Vec::new(); $crate::prim_comp!{@adv comp, $simplex; $($tail)*} diff --git a/src/relative.rs b/src/map/relative.rs similarity index 97% rename from src/relative.rs rename to src/map/relative.rs index ac51f07b5..8112c2bdb 100644 --- a/src/relative.rs +++ b/src/map/relative.rs @@ -1,13 +1,15 @@ -use crate::ops::{BinaryComposition, BinaryProduct, UniformComposition, UniformConcat, UniformProduct}; -use crate::primitive::{ +use super::ops::{ + BinaryComposition, BinaryProduct, UniformComposition, UniformConcat, UniformProduct, +}; +use super::primitive::{ AllPrimitiveDecompositions, Identity, Primitive, PrimitiveDecompositionIter, Slice, SwapPrimitiveComposition, UnboundedMap, WithBounds, }; +use super::{AddOffset, Error, Map, UnapplyIndicesData}; use crate::util::ReplaceNthIter as _; -use crate::{AddOffset, Map, UnapplyIndicesData}; use std::collections::BTreeMap; use std::iter; -use std::rc::Rc; +use std::sync::Arc; impl AllPrimitiveDecompositions for UniformProduct> where @@ -233,7 +235,7 @@ impl Map for Relative { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} - dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Result} dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} dispatch! {fn apply_index(&self, index: usize) -> Option} dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} @@ -302,7 +304,7 @@ impl UnapplyIndicesData for IndexOutIn { #[derive(Debug, Clone, PartialEq)] pub struct RelativeToConcat { rels: Vec, - index_map: Rc>, + index_map: Arc>, //common: Vec, len_out: usize, len_in: usize, @@ -335,7 +337,7 @@ impl Map for RelativeToConcat { // .apply_inplace(index, coordinates, stride, offset); let (iout, iin) = self.index_map[index]; let n = self.index_map.len(); - self.rels[iin / n].apply_inplace(iin % n, coordinates, stride, offset); + self.rels[iin / n].apply_inplace_unchecked(iin % n, coordinates, stride, offset); iout } fn apply_index_unchecked(&self, index: usize) -> usize { @@ -399,9 +401,9 @@ where WithBounds::from_input(slice, rel.dim_out(), rel.len_out()).unwrap(); let slice = Relative::Slice(slice); let rel = Relative::Map(rel); - return Some(Relative::Composition(UniformComposition::new_unchecked(vec![ - slice, rel, - ]))); + return Some(Relative::Composition(UniformComposition::new_unchecked( + vec![slice, rel], + ))); } PartialRelative::Some(rel, indices) => { rels_indices.push((rel, offset, indices)); diff --git a/src/tesselation.rs b/src/map/tesselation.rs similarity index 93% rename from src/tesselation.rs rename to src/map/tesselation.rs index 12775970d..a0fcd783d 100644 --- a/src/tesselation.rs +++ b/src/map/tesselation.rs @@ -1,11 +1,11 @@ -use crate::ops::UniformConcat; -use crate::primitive::{ +use super::ops::UniformConcat; +use super::primitive::{ AllPrimitiveDecompositions, Primitive, PrimitiveDecompositionIter, WithBounds, }; -use crate::relative::RelativeTo; +use super::relative::RelativeTo; +use super::{AddOffset, Error, Map, UnapplyIndicesData}; use crate::simplex::Simplex; use crate::util::{ReplaceNthIter, SkipNthIter}; -use crate::{AddOffset, Error, Map, UnapplyIndicesData}; use std::iter; use std::ops::Mul; @@ -104,6 +104,13 @@ impl UniformTesselation { .map(|shape| Primitive::new_uniform_points(shape.centroid(), shape.dim())); self.extend(primitives, Vec::new(), self.len_in()) } + pub fn vertices(&self) -> Self { + let primitives = self + .shapes + .iter() + .map(|shape| Primitive::new_uniform_points(shape.vertices(), shape.dim())); + self.extend(primitives, Vec::new(), self.len_in()) + } } //impl Deref for UniformTesselation { @@ -140,7 +147,7 @@ impl Map for UniformTesselation { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} - dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Result} dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} dispatch! {fn apply_index(&self, index: usize) -> Option} dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} @@ -246,11 +253,21 @@ impl Tesselation { )) } pub fn vertices(&self) -> Self { - unimplemented! {} + Self(UniformConcat::new_unchecked( + self.0.iter().map(|item| item.vertices()).collect(), + )) } - pub fn apply_inplace(&self, index: usize, coords: &mut [f64], stride: usize) -> Option { + pub fn apply_inplace( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + ) -> Result { self.0.apply_inplace(index, coords, stride, 0) } + pub fn apply_index(&self, index: usize) -> Option { + self.0.apply_index(index) + } pub fn unapply_indices(&self, indices: &[T]) -> Option> { self.0.unapply_indices(indices) } @@ -290,7 +307,7 @@ impl Map for Tesselation { dispatch! {fn dim_in(&self) -> usize} dispatch! {fn delta_dim(&self) -> usize} dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} - dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Option} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Result} dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} dispatch! {fn apply_index(&self, index: usize) -> Option} dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} From 8c4b3b396bb9f2452a5535d2861b3b1a3373fd72 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 6 Jul 2022 13:04:27 +0200 Subject: [PATCH 35/45] WIP --- src/lib.rs | 128 ++++++++++++++++++++++++++--------- src/map/mod.rs | 2 +- src/map/ops.rs | 3 + src/map/primitive.rs | 41 +++++++++++- src/map/relative.rs | 147 +++++++++++++++++++++++------------------ src/map/tesselation.rs | 10 +-- 6 files changed, 227 insertions(+), 104 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bea6b6a30..e46e8a3f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ mod util; use map::tesselation::Tesselation; use map::Map; +use map::relative::RelativeTo; use numpy::{IntoPyArray, IxDyn, PyArray, PyArrayDyn, PyReadonlyArrayDyn}; use pyo3::exceptions::{PyIndexError, PyValueError}; use pyo3::prelude::*; @@ -78,6 +79,41 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; + fn apply_map_from_numpy<'py>(py: Python<'py>, map: &impl Map, index: usize, coords: PyReadonlyArrayDyn) -> PyResult<(usize, &'py PyArrayDyn)> { + if coords.ndim() == 0 { + return Err(PyValueError::new_err( + "the `coords` argument must have at least one dimension", + )); + } + if coords.shape()[coords.ndim() - 1] != map.dim_in() { + return Err(PyValueError::new_err(format!( + "the last axis of the `coords` argument should have dimension {}", + map.dim_in() + ))); + } + let mut result: Vec = coords + .as_array() + .rows() + .into_iter() + .flat_map(|row| { + row.into_iter() + .cloned() + .chain(iter::repeat(0.0).take(map.delta_dim())) + }) + .collect(); + let index = map.apply_inplace(index, &mut result, map.dim_out(), 0)?; + let result = PyArray::from_vec(py, result); + let shape: Vec = coords + .shape() + .iter() + .take(coords.ndim() - 1) + .cloned() + .chain(iter::once(map.dim_out())) + .collect(); + let result = result.reshape(&shape[..])?; + Ok((index, result)) + } + #[pyclass(name = "Tesselation", module = "nutils._rust")] #[derive(Debug, Clone)] struct PyTesselation(Tesselation); @@ -89,6 +125,9 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { let shapes = shapes.iter().map(|shape| shape.into()).collect(); Tesselation::identity(shapes, len).into() } + pub fn __repr__(&self) -> String { + format!("{:?}", self.0) + } pub fn __len__(&self) -> usize { self.0.len() } @@ -132,38 +171,16 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { index: usize, coords: PyReadonlyArrayDyn, ) -> PyResult<(usize, &'py PyArrayDyn)> { - if coords.ndim() == 0 { - return Err(PyValueError::new_err( - "the `coords` argument must have at least one dimension", - )); - } - if coords.shape()[coords.ndim() - 1] != self.0.dim() { - return Err(PyValueError::new_err(format!( - "the last axis of the `coords` argument should have dimension {}", - self.0.dim() - ))); - } - let mut result: Vec = coords - .as_array() - .rows() - .into_iter() - .flat_map(|row| { - row.into_iter() - .cloned() - .chain(iter::repeat(0.0).take(self.0.delta_dim())) - }) - .collect(); - let index = self.0.apply_inplace(index, &mut result, self.0.dim_out())?; - let result = PyArray::from_vec(py, result); - let shape: Vec = coords - .shape() - .iter() - .take(coords.ndim() - 1) - .cloned() - .chain(iter::once(self.0.dim_out())) - .collect(); - let result = result.reshape(&shape[..])?; - Ok((index, result)) + apply_map_from_numpy(py, &self.0, index, coords) + } + //pub fn unapply_indices(&self, indices: Vec) -> PyResult> { + // self.0 + // .unapply_indices(&indices) + // .map(|mut indices| { indices.sort(); indices }) + // .ok_or(PyValueError::new_err("index out of range")) + //} + pub fn relative_to(&self, target: &Self) -> PyResult { + self.0.relative_to(&target.0).map(|rel| PyMap(rel)).ok_or(PyValueError::new_err("cannot make relative")) } } @@ -175,5 +192,52 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; + #[pyclass(name = "Map", module = "nutils._rust")] + #[derive(Debug, Clone)] + struct PyMap(>::Output); + + #[pymethods] + impl PyMap { + pub fn __repr__(&self) -> String { + format!("{:?}", self.0) + } + pub fn len_out(&self) -> usize { + self.0.len_out() + } + pub fn len_in(&self) -> usize { + self.0.len_in() + } + pub fn dim_out(&self) -> usize { + self.0.dim_out() + } + pub fn dim_in(&self) -> usize { + self.0.dim_in() + } + pub fn delta_dim(&self) -> usize { + self.0.delta_dim() + } + pub fn apply_index(&self, index: usize) -> PyResult { + self.0 + .apply_index(index) + .ok_or(PyIndexError::new_err("index out of range")) + } + pub fn apply<'py>( + &self, + py: Python<'py>, + index: usize, + coords: PyReadonlyArrayDyn, + ) -> PyResult<(usize, &'py PyArrayDyn)> { + apply_map_from_numpy(py, &self.0, index, coords) + } + pub fn unapply_indices(&self, indices: Vec) -> PyResult> { + self.0 + .unapply_indices(&indices) + .map(|mut indices| { indices.sort(); indices }) + .ok_or(PyValueError::new_err("index out of range")) + } + } + + m.add_class::()?; + Ok(()) } diff --git a/src/map/mod.rs b/src/map/mod.rs index 074212d1a..85191d7fe 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -159,7 +159,7 @@ macro_rules! assert_map_apply { } assert_eq!( item.apply_inplace($inidx, &mut work, stride, 0), - Some($outidx), + Ok($outidx), "apply_inplace", ); assert_eq!(item.apply_index($inidx), Some($outidx), "apply_index"); diff --git a/src/map/ops.rs b/src/map/ops.rs index d5bc1c5a5..c6e261815 100644 --- a/src/map/ops.rs +++ b/src/map/ops.rs @@ -22,6 +22,9 @@ impl BinaryComposition { Ok(Self(outer, inner)) } } + pub fn new_unchecked(outer: Outer, inner: Inner) -> Self { + Self::new(outer, inner).unwrap() + } /// Returns the outer map of the composition. pub fn outer(&self) -> &Outer { &self.0 diff --git a/src/map/primitive.rs b/src/map/primitive.rs index c2c1cb030..c5ddb0d14 100644 --- a/src/map/primitive.rs +++ b/src/map/primitive.rs @@ -896,7 +896,9 @@ impl SwapPrimitiveComposition for [Primitive] { inner: &Primitive, stride: usize, ) -> Option<((Primitive, usize), Self::Output)> { - if inner.is_transpose() { + if self.is_empty() { + return Some(((inner.clone(), stride), Vec::new())); + } else if inner.is_transpose() { return None; } let mut target = inner.clone(); @@ -1051,6 +1053,43 @@ pub trait AllPrimitiveDecompositions: Sized { /// assert_eq!(iter.next(), None); /// ``` fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self>; + fn as_transposes(&self) -> Option> + where + Self: Map, + { + let mut transposes = Vec::new(); + if self.is_identity() { + return Some(Vec::new()); + } + let mut next = |m: &Self| if let Some(((Primitive::Transpose(transpose), stride), rhs)) = m.all_primitive_decompositions().next() { + let len = transpose.mod_out(); + if stride != 1 { + unimplemented!{} + transposes.push(Transpose::new(stride, len)); + } + transposes.push(transpose); + if stride != 1 { + unimplemented!{} + transposes.push(Transpose::new(len, stride)); + } + Some(rhs) + } else { + None + }; + let mut rhs = if let Some(rhs) = next(self) { + rhs + } else { + return None; + }; + while !rhs.is_identity() { + rhs = if let Some(rhs) = next(&rhs) { + rhs + } else { + return None; + }; + } + Some(transposes) + } } impl AllPrimitiveDecompositions for Vec { diff --git a/src/map/relative.rs b/src/map/relative.rs index 8112c2bdb..b90a3b10f 100644 --- a/src/map/relative.rs +++ b/src/map/relative.rs @@ -3,7 +3,7 @@ use super::ops::{ }; use super::primitive::{ AllPrimitiveDecompositions, Identity, Primitive, PrimitiveDecompositionIter, Slice, - SwapPrimitiveComposition, UnboundedMap, WithBounds, + SwapPrimitiveComposition, UnboundedMap, WithBounds, Transpose, }; use super::{AddOffset, Error, Map, UnapplyIndicesData}; use crate::util::ReplaceNthIter as _; @@ -11,59 +11,59 @@ use std::collections::BTreeMap; use std::iter; use std::sync::Arc; -impl AllPrimitiveDecompositions for UniformProduct> -where - M: Map + AllPrimitiveDecompositions + Clone, -{ - fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { - Box::new( - self.iter() - .enumerate() - .zip(self.offsets_out()) - .zip(self.strides_out()) - .flat_map(move |(((iprod, term), prod_offset), prod_stride)| { - term.all_primitive_decompositions().into_iter().map( - move |((mut prim, mut stride), inner)| { - prim.add_offset(prod_offset); - if prim.mod_out() == 1 { - stride = 1; - } else { - stride *= prod_stride; - } - let product = self.iter().cloned().replace_nth(iprod, inner).collect(); - ((prim, stride), product) - }, - ) - }), - ) - } -} +//impl AllPrimitiveDecompositions for UniformProduct> +//where +// M: Map + AllPrimitiveDecompositions + Clone, +//{ +// fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { +// Box::new( +// self.iter() +// .enumerate() +// .zip(self.offsets_out()) +// .zip(self.strides_out()) +// .flat_map(move |(((iprod, term), prod_offset), prod_stride)| { +// term.all_primitive_decompositions().into_iter().map( +// move |((mut prim, mut stride), inner)| { +// prim.add_offset(prod_offset); +// if prim.mod_out() == 1 { +// stride = 1; +// } else { +// stride *= prod_stride; +// } +// let product = self.iter().cloned().replace_nth(iprod, inner).collect(); +// ((prim, stride), product) +// }, +// ) +// }), +// ) +// } +//} -impl AllPrimitiveDecompositions for BinaryProduct -where - M0: Map + AllPrimitiveDecompositions + Clone, - M1: Map + AllPrimitiveDecompositions + Clone, -{ - fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { - let first = self.first().all_primitive_decompositions().into_iter().map( - |((prim, mut stride), first)| { - stride *= self.second().len_out(); - let product = BinaryProduct::new(first, self.second().clone()); - ((prim, stride), product) - }, - ); - let second = self - .second() - .all_primitive_decompositions() - .into_iter() - .map(|((mut prim, stride), second)| { - prim.add_offset(self.first().dim_out()); - let product = BinaryProduct::new(self.first().clone(), second); - ((prim, stride), product) - }); - Box::new(first.chain(second)) - } -} +//impl AllPrimitiveDecompositions for BinaryProduct +//where +// M0: Map + AllPrimitiveDecompositions + Clone, +// M1: Map + AllPrimitiveDecompositions + Clone, +//{ +// fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { +// let first = self.first().all_primitive_decompositions().into_iter().map( +// |((prim, mut stride), first)| { +// stride *= self.second().len_out(); +// let product = BinaryProduct::new(first, self.second().clone()); +// ((prim, stride), product) +// }, +// ); +// let second = self +// .second() +// .all_primitive_decompositions() +// .into_iter() +// .map(|((mut prim, stride), second)| { +// prim.add_offset(self.first().dim_out()); +// let product = BinaryProduct::new(self.first().clone(), second); +// ((prim, stride), product) +// }); +// Box::new(first.chain(second)) +// } +//} // TODO: UniformComposition? @@ -135,11 +135,10 @@ where .filter_map(|(key, t1)| outers2.remove(&key).map(|t2| (key, t1, t2))) .next() { - let outer_mod_out = outer.mod_out(); - common.push(outer); - if stride != 1 { + if stride != 1 && outer.mod_out() != 1 { common.push(Primitive::new_transpose(stride, outer_mod_out)); } + common.push(outer); (map1, map2) } else { break; @@ -158,9 +157,13 @@ where { let (_, rem, rel) = decompose_common(target, source); if rem.is_identity() { - PartialRelative::AllSameOrder(rel) + PartialRelative::All(rel, None) + } else if let Some(mut transposes) = rem.as_transposes() { + transposes.reverse(); + transposes.iter_mut().for_each(|transpose| transpose.reverse()); + let transposes = WithBounds::new_unchecked(transposes, rem.dim_in(), rem.len_in()); + PartialRelative::All(rel, Some(transposes)) } else if rem.is_index_map() { - // TODO: transfer transposes from `rem` to `rel` if this would make `rem` the identity let mut indices: Vec = (0..rem.len_in()).collect(); rem.apply_indices_inplace_unchecked(&mut indices); PartialRelative::Some(rel, indices) @@ -170,7 +173,7 @@ where } enum PartialRelative { - AllSameOrder(M), + All(M, Option>>), Some(M, Vec), CannotEstablishRelation, } @@ -196,6 +199,7 @@ pub enum Relative { Identity(WithBounds), Slice(WithBounds), Map(M), + TransposedMap(BinaryComposition>, M>), Composition(UniformComposition), RelativeToConcat(RelativeToConcat), } @@ -222,6 +226,7 @@ macro_rules! dispatch { Self::Identity(var) => var.$fn($($arg),*), Self::Slice(var) => var.$fn($($arg),*), Self::Map(var) => var.$fn($($arg),*), + Self::TransposedMap(var) => var.$fn($($arg),*), Self::Composition(var) => var.$fn($($arg),*), Self::RelativeToConcat(var) => var.$fn($($arg),*), } @@ -251,12 +256,20 @@ impl Map for Relative { //} impl RelativeTo for WithBounds> { - type Output = Self; + type Output = Relative; - fn relative_to(&self, target: &Self) -> Option { + fn relative_to(&self, target: &Self) -> Option { let (_, rem, rel) = decompose_common(target.clone(), self.clone()); - // TODO: transfer transposes from `rem` to `rel` - rem.is_identity().then(|| rel) + if rem.is_identity() { + Some(Relative::Map(rel)) + } else if let Some(mut transposes) = rem.as_transposes() { + transposes.reverse(); + transposes.iter_mut().for_each(|transpose| transpose.reverse()); + let transposes = WithBounds::new_unchecked(transposes, rem.dim_in(), rem.len_in()); + Some(Relative::TransposedMap(BinaryComposition::new_unchecked(transposes, rel))) + } else { + None + } } } @@ -395,12 +408,16 @@ where for target in targets.iter().cloned() { let new_offset = offset + target.len_in(); match partial_relative_to(self.clone(), target) { - PartialRelative::AllSameOrder(rel) => { + PartialRelative::All(rel, transposes) => { let slice = Slice::new(offset, rel.len_out(), targets.len_in()); let slice = WithBounds::from_input(slice, rel.dim_out(), rel.len_out()).unwrap(); let slice = Relative::Slice(slice); - let rel = Relative::Map(rel); + let rel = if let Some(transposes) = transposes { + Relative::TransposedMap(BinaryComposition::new_unchecked(transposes, rel)) + } else { + Relative::Map(rel) + }; return Some(Relative::Composition(UniformComposition::new_unchecked( vec![slice, rel], ))); diff --git a/src/map/tesselation.rs b/src/map/tesselation.rs index a0fcd783d..287872c48 100644 --- a/src/map/tesselation.rs +++ b/src/map/tesselation.rs @@ -173,7 +173,7 @@ impl AllPrimitiveDecompositions for UniformTesselation { } impl RelativeTo for UniformTesselation { - type Output = WithBounds>; + type Output = > as RelativeTo>>>::Output; fn relative_to(&self, target: &Self) -> Option { self.map.relative_to(&target.map) @@ -399,13 +399,13 @@ mod tests { let stride = 2; let mut work: Vec<_> = iter::repeat(-1.0).take(stride).collect(); println!("tess: {tess:?}"); - assert_eq!(centroids.apply_inplace(0, &mut work, stride), Some(0)); + assert_eq!(centroids.apply_inplace(0, &mut work, stride), Ok(0)); assert_abs_diff_eq!(work[..], [0.25, 1.0]); - assert_eq!(centroids.apply_inplace(1, &mut work, stride), Some(0)); + assert_eq!(centroids.apply_inplace(1, &mut work, stride), Ok(0)); assert_abs_diff_eq!(work[..], [0.25, 0.0]); - assert_eq!(centroids.apply_inplace(2, &mut work, stride), Some(0)); + assert_eq!(centroids.apply_inplace(2, &mut work, stride), Ok(0)); assert_abs_diff_eq!(work[..], [0.75, 1.0]); - assert_eq!(centroids.apply_inplace(3, &mut work, stride), Some(0)); + assert_eq!(centroids.apply_inplace(3, &mut work, stride), Ok(0)); assert_abs_diff_eq!(work[..], [0.75, 0.0]); } From db895dc8db954d1566018d4d01e763599a5af879 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 6 Jul 2022 16:43:38 +0200 Subject: [PATCH 36/45] use args instead of deps in default deriv impl The `WithDerivative` evaluable defines a derivative of a wrapped array to some target. The target is not included in the `Evaluable.dependencies`, but is included in the `Array.arguments`. The current default implementation of `Array._derivative` incorrectly returns zeros if the derivative target is not found in `self.dependencies`. This patch fixes the bug by testing for existence in `self.arguments`. --- nutils/evaluable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nutils/evaluable.py b/nutils/evaluable.py index d82ce2631..fd7624723 100644 --- a/nutils/evaluable.py +++ b/nutils/evaluable.py @@ -990,7 +990,7 @@ def _unaligned(self): _inflations = () def _derivative(self, var, seen): - if self.dtype in (bool, int) or var not in self.dependencies: + if self.dtype in (bool, int) or var not in self.arguments: return Zeros(self.shape + var.shape, dtype=self.dtype) raise NotImplementedError('derivative not defined for {}'.format(self.__class__.__name__)) From 697c6cccffdecca5bcf36ad7237b64a0f983369c Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 6 Jul 2022 16:21:49 +0200 Subject: [PATCH 37/45] change lowerargs to include full transforms seq This patch changes the `transform_chains` part of the lower args from a list of `EvaluableTransformChain`s to a tuple of a list of transforms sequences (`transformseq.Transforms`) and an evaluable index (`Array`). The relevant operations defined on the `EvaluableTransformChains` are now defined as subclasses of `evaluable.Array`, taking (unevaluable) transforms sequences, the evaluable index and evaluable coordinates as constructor arguments. --- nutils/evaluable.py | 128 +++++++++++++++++++++++++++++++++++++++++ nutils/function.py | 49 +++++++++------- nutils/sample.py | 10 ++-- nutils/topology.py | 13 ++--- tests/test_function.py | 11 ++-- tests/test_sample.py | 6 +- tests/test_topology.py | 9 +-- 7 files changed, 180 insertions(+), 46 deletions(-) diff --git a/nutils/evaluable.py b/nutils/evaluable.py index fd7624723..79d208eb6 100644 --- a/nutils/evaluable.py +++ b/nutils/evaluable.py @@ -3878,6 +3878,134 @@ def _intbounds_impl(self): return 0, upper_length - 1 +class TransformsRootCoords(Array): + + def __init__(self, transforms, index: Array, coords: Array): + if index.dtype != int or index.ndim != 0: + raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + if coords.dtype != float: + raise ValueError('argument `coords` must be a real-valued array with at least one axis') + self._transforms = transforms + self._index = index + self._coords = coords + super().__init__(args=[index, coords], shape=(*coords.shape[:-1], transforms.todims), dtype=float) + + def evalf(self, index, coords): + chain = self._transforms[index.__index__()] + return functools.reduce(lambda c, t: t.apply(c), reversed(chain), coords) + + def _derivative(self, var, seen): + linear = TransformsRootLinear(self._transforms, self._index) + dcoords = derivative(self._coords, var, seen) + return einsum('ij,AjB->AiB', linear, dcoords, A=self._coords.ndim - 1, B=var.ndim) + + +class TransformsRelativeCoords(Array): + + def __init__(self, target, transforms, index: Array, coords: Array): + if index.dtype != int or index.ndim != 0: + raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + if coords.dtype != float: + raise ValueError('argument `coords` must be a real-valued array with at least one axis') + self._target = target + self._transforms = transforms + self._index = index + self._coords = coords + super().__init__(args=[index, coords], shape=(*coords.shape[:-1], target.fromdims), dtype=float) + + def evalf(self, index, coords): + _, chain = self._target.index_with_tail(self._transforms[index.__index__()]) + return functools.reduce(lambda c, t: t.apply(c), reversed(chain), coords) + + def _derivative(self, var, seen): + linear = TransformsRelativeLinear(self._target, self._transforms, self._index) + dcoords = derivative(self._coords, var, seen) + return einsum('ij,AjB->AiB', linear, dcoords, A=self._coords.ndim - 1, B=var.ndim) + + def _simplified(self): + if self._target == self._transforms: + return self._coords + + +class TransformsRelativeIndex(Array): + + def __init__(self, target, transforms, index: Array): + if index.dtype != int or index.ndim != 0: + raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + self._target = target + self._transforms = transforms + self._index = index + super().__init__(args=[index], shape=(), dtype=int) + + def evalf(self, index): + index, _ = self._target.index_with_tail(self._transforms[index.__index__()]) + return numpy.array(index) + + def _intbounds_impl(self): + return 0, len(self._target) - 1 + + def _simplified(self): + if self._target == self._transforms: + return self._index + + +class TransformsRootLinear(Array): + + def __init__(self, transforms, index: Array): + if index.dtype != int or index.ndim != 0: + raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + self._transforms = transforms + super().__init__(args=[index], shape=(transforms.todims, transforms.fromdims), dtype=float) + + def evalf(self, index): + chain = self._transforms[index.__index__()] + if chain: + return functools.reduce(lambda r, i: i @ r, (item.linear for item in reversed(chain))) + else: + return numpy.eye(self._transforms.fromdims) + + +class TransformsRelativeLinear(Array): + + def __init__(self, target, transforms, index: Array): + if index.dtype != int or index.ndim != 0: + raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + self._target = target + self._transforms = transforms + super().__init__(args=[index], shape=(target.fromdims, transforms.fromdims), dtype=float) + + def evalf(self, index): + _, chain = self._target.index_with_tail(self._transforms[index.__index__()]) + if chain: + return functools.reduce(lambda r, i: i @ r, (item.linear for item in reversed(chain))) + else: + return numpy.eye(self._transforms.fromdims) + + def _simplified(self): + if self._target == self._transforms: + return diagonalize(ones((self._transforms.fromdims,))) + + +class TransformsRootBasis(Array): + + def __init__(self, transforms, index: Array): + if index.dtype != int or index.ndim != 0: + raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + self._transforms = transforms + super().__init__(args=[index], shape=(transforms.todims, transforms.todims), dtype=float) + + def evalf(self, index): + chain = self._transforms[index.__index__()] + linear = numpy.eye(self._transforms.fromdims) + for item in reversed(chain): + linear = item.linear @ linear + assert item.fromdims <= item.todims <= item.fromdims + 1 + if item.todims == item.fromdims + 1: + linear = numpy.concatenate([linear, item.ext[:, numpy.newaxis]], axis=1) + assert linear.shape == (self._transforms.todims, self._transforms.todims) + return linear + + class _LoopIndex(Argument): __slots__ = 'length' diff --git a/nutils/function.py b/nutils/function.py index 45dd42c5e..819925782 100644 --- a/nutils/function.py +++ b/nutils/function.py @@ -27,13 +27,14 @@ class LowerArgs(NamedTuple): points_shape : :class:`tuple` of scalar, integer :class:`nutils.evaluable.Array` The shape of the leading points axes that are to be added to the lowered :class:`nutils.evaluable.Array`. - transform_chains : mapping of :class:`str` to :class:`nutils.transform.EvaluableTransformChain` pairs + transform_chains : mapping of :class:`str` to tuples of :class:`nutils.transformseq.Transforms` and :class:`nutils.evaluable.Array` + A mapping of spaces to transforms sequences and evaluable indices. coordinates : mapping of :class:`str` to :class:`nutils.evaluable.Array` objects The coordinates at which the function will be evaluated. ''' points_shape: Tuple[evaluable.Array, ...] - transform_chains: Mapping[str, Tuple[EvaluableTransformChain, EvaluableTransformChain]] + transform_chains: Mapping[str, Tuple[Transforms, evaluable.Array]] coordinates: Mapping[str, evaluable.Array] def consistent(self): @@ -43,8 +44,10 @@ def consistent(self): for space, coords in self.coordinates.items()) @classmethod - def for_space(cls, space: str, transform_chains: Tuple[EvaluableTransformChain, EvaluableTransformChain], coordinates: evaluable.Array) -> 'LowerArgs': - return cls(coordinates.shape[:-1], {space: transform_chains}, {space: coordinates}) + def for_space(cls, space: str, transforms: Tuple[Transforms, ...], index: evaluable.Array, coordinates: evaluable.Array) -> 'LowerArgs': + if index.dtype != int or index.ndim != 0: + raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + return cls(coordinates.shape[:-1], {space: (transforms, index)}, {space: coordinates}) def __or__(self, other: 'LowerArgs') -> 'LowerArgs': duplicates = set(self.transform_chains) & set(other.transform_chains) @@ -944,7 +947,8 @@ def __init__(self, arg: Array, space: str) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: oppargs = LowerArgs(args.points_shape, dict(args.transform_chains), args.coordinates) - oppargs.transform_chains[self._space] = args.transform_chains[self._space][::-1] + transforms, index = args.transform_chains[self._space] + oppargs.transform_chains[self._space] = transforms[::-1], index return self._arg.lower(oppargs) @@ -959,7 +963,8 @@ def lower(self, args: LowerArgs) -> evaluable.Array: inv_linear = evaluable.prependaxes(inv_linear, args.points_shape) tip_coords = args.coordinates[self._space] tip_coords = evaluable.WithDerivative(tip_coords, _tip_derivative_target(self._space, tip_coords.shape[-1]), evaluable.Diagonalize(evaluable.ones(tip_coords.shape))) - coords = args.transform_chains[self._space][0].apply(tip_coords) + transforms, index = args.transform_chains[self._space] + coords = evaluable.TransformsRootCoords(transforms[0], index, tip_coords) return evaluable.WithDerivative(coords, _root_derivative_target(self._space, self.shape[0]), inv_linear) @@ -971,8 +976,8 @@ def __init__(self, space: str, transforms: Transforms) -> None: super().__init__((), int, frozenset({space}), {}) def lower(self, args: LowerArgs) -> evaluable.Array: - index, tail = args.transform_chains[self._space][0].index_with_tail_in(self._transforms) - return evaluable.prependaxes(index, args.points_shape) + transforms, index = args.transform_chains[self._space] + return evaluable.prependaxes(evaluable.TransformsRelativeIndex(self._transforms, transforms[0], index), args.points_shape) class _TransformsCoords(Array): @@ -983,9 +988,9 @@ def __init__(self, space: str, transforms: Transforms) -> None: super().__init__((transforms.fromdims,), float, frozenset({space}), {}) def lower(self, args: LowerArgs) -> evaluable.Array: - index, tail = args.transform_chains[self._space][0].index_with_tail_in(self._transforms) - head = self._transforms.get_evaluable(index) - L = head.linear + transforms, tip_index = args.transform_chains[self._space] + index = evaluable.TransformsRelativeIndex(self._transforms, transforms[0], tip_index) + L = evaluable.TransformsRootLinear(self._transforms, index) if self._transforms.todims > self._transforms.fromdims: LTL = evaluable.einsum('ki,kj->ij', L, L) Linv = evaluable.einsum('ik,jk->ij', evaluable.inverse(LTL), L) @@ -994,7 +999,7 @@ def lower(self, args: LowerArgs) -> evaluable.Array: Linv = evaluable.prependaxes(Linv, args.points_shape) tip_coords = args.coordinates[self._space] tip_coords = evaluable.WithDerivative(tip_coords, _tip_derivative_target(self._space, tip_coords.shape[-1]), evaluable.Diagonalize(evaluable.ones(tip_coords.shape))) - coords = tail.apply(tip_coords) + coords = evaluable.TransformsRelativeCoords(self._transforms, transforms[0], tip_index, tip_coords) return evaluable.WithDerivative(coords, _root_derivative_target(self._space, self._transforms.todims), Linv) @@ -1036,10 +1041,10 @@ def __init__(self, func: Array, geom: Array) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: func = self._func.lower(args) geom = self._geom.lower(args) - ref_dim = builtins.sum(args.transform_chains[space][0].todims for space in self._geom.spaces) + ref_dim = builtins.sum(args.transform_chains[space][0][0].todims for space in self._geom.spaces) if self._geom.shape[-1] != ref_dim: raise Exception('cannot invert {}x{} jacobian'.format(self._geom.shape[-1], ref_dim)) - refs = tuple(_root_derivative_target(space, chain.todims) for space, (chain, opposite) in args.transform_chains.items() if space in self._geom.spaces) + refs = tuple(_root_derivative_target(space, chain.todims) for space, ((chain, *_), index) in args.transform_chains.items() if space in self._geom.spaces) dfunc_dref = evaluable.concatenate([evaluable.derivative(func, ref) for ref in refs], axis=-1) dgeom_dref = evaluable.concatenate([evaluable.derivative(geom, ref) for ref in refs], axis=-1) dref_dgeom = evaluable.inverse(dgeom_dref) @@ -1062,10 +1067,10 @@ def __init__(self, func: Array, geom: Array) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: func = self._func.lower(args) geom = self._geom.lower(args) - ref_dim = builtins.sum(args.transform_chains[space][0].fromdims for space in self._geom.spaces) + ref_dim = builtins.sum(args.transform_chains[space][0][0].fromdims for space in self._geom.spaces) if self._geom.shape[-1] != ref_dim + 1: raise ValueError('expected a {}d geometry but got a {}d geometry'.format(ref_dim + 1, self._geom.shape[-1])) - refs = tuple((_root_derivative_target if chain.todims == chain.fromdims else _tip_derivative_target)(space, chain.fromdims) for space, (chain, opposite) in args.transform_chains.items() if space in self._geom.spaces) + refs = tuple((_root_derivative_target if chain.todims == chain.fromdims else _tip_derivative_target)(space, chain.fromdims) for space, ((chain, *_), index) in args.transform_chains.items() if space in self._geom.spaces) dfunc_dref = evaluable.concatenate([evaluable.derivative(func, ref) for ref in refs], axis=-1) dgeom_dref = evaluable.concatenate([evaluable.derivative(geom, ref) for ref in refs], axis=-1) dref_dgeom = evaluable.einsum('Ajk,Aik->Aij', dgeom_dref, evaluable.inverse(evaluable.grammium(dgeom_dref))) @@ -1090,14 +1095,14 @@ def __init__(self, geom: Array, tip_dim: Optional[int] = None) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: geom = self._geom.lower(args) - tip_dim = builtins.sum(args.transform_chains[space][0].fromdims for space in self._geom.spaces) + tip_dim = builtins.sum(args.transform_chains[space][0][0].fromdims for space in self._geom.spaces) if self._tip_dim is not None and self._tip_dim != tip_dim: raise ValueError('Expected a tip dimension of {} but got {}.'.format(self._tip_dim, tip_dim)) if self._geom.shape[-1] < tip_dim: raise ValueError('the dimension of the geometry cannot be lower than the dimension of the tip coords') if not self._geom.spaces: return evaluable.ones(geom.shape[:-1]) - tips = [_tip_derivative_target(space, chain.fromdims) for space, (chain, opposite) in args.transform_chains.items() if space in self._geom.spaces] + tips = [_tip_derivative_target(space, chain.fromdims) for space, ((chain, *_), index) in args.transform_chains.items() if space in self._geom.spaces] J = evaluable.concatenate([evaluable.derivative(geom, tip) for tip in tips], axis=-1) return evaluable.sqrt_abs_det_gram(J) @@ -1111,8 +1116,8 @@ def __init__(self, geom: Array) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: geom = self._geom.lower(args) - spaces_dim = builtins.sum(args.transform_chains[space][0].todims for space in self._geom.spaces) - normal_dim = spaces_dim - builtins.sum(args.transform_chains[space][0].fromdims for space in self._geom.spaces) + spaces_dim = builtins.sum(args.transform_chains[space][0][0].todims for space in self._geom.spaces) + normal_dim = spaces_dim - builtins.sum(args.transform_chains[space][0][0].fromdims for space in self._geom.spaces) if self._geom.shape[-1] != spaces_dim: raise ValueError('The dimension of geometry must equal the sum of the dimensions of the given spaces.') if normal_dim == 0: @@ -1121,7 +1126,7 @@ def lower(self, args: LowerArgs) -> evaluable.Array: raise ValueError('Cannot unambiguously compute the normal because the dimension of the normal space is larger than one.') tangents = [] normal = None - for space, (chain, opposite) in args.transform_chains.items(): + for space, ((chain, *_), index) in args.transform_chains.items(): if space not in self._geom.spaces: continue rgrad = evaluable.derivative(geom, _root_derivative_target(space, chain.todims)) @@ -1130,7 +1135,7 @@ def lower(self, args: LowerArgs) -> evaluable.Array: tangents.append(rgrad) else: assert normal is None and chain.todims == chain.fromdims + 1 - basis = evaluable.einsum('Aij,jk->Aik', rgrad, chain.basis) + basis = evaluable.einsum('Aij,jk->Aik', rgrad, evaluable.TransformsRootBasis(chain, index)) tangents.append(basis[..., :chain.fromdims]) normal = basis[..., chain.fromdims:] assert normal is not None diff --git a/nutils/sample.py b/nutils/sample.py index f3f3dfeb7..8dfd2c91f 100644 --- a/nutils/sample.py +++ b/nutils/sample.py @@ -28,7 +28,7 @@ import abc _PointsShape = Tuple[evaluable.Array, ...] -_TransformChainsMap = Mapping[str, Tuple[EvaluableTransformChain, EvaluableTransformChain]] +_TransformChainsMap = Mapping[str, Tuple[Tuple[Transforms, ...], int]] _CoordinatesMap = Mapping[str, evaluable.Array] @@ -397,7 +397,7 @@ def get_evaluable_weights(self, __ielem: evaluable.Array) -> evaluable.Array: return self.points.get_evaluable_weights(__ielem) def get_lower_args(self, __ielem: evaluable.Array) -> function.LowerArgs: - return function.LowerArgs.for_space(self.space, tuple(t.get_evaluable(__ielem) for t in (self.transforms*2)[:2]), self.points.get_evaluable_coords(__ielem)) + return function.LowerArgs.for_space(self.space, self.transforms, __ielem, self.points.get_evaluable_coords(__ielem)) def basis(self) -> function.Array: return _Basis(self) @@ -925,9 +925,9 @@ def lower(self, args: function.LowerArgs) -> evaluable.Array: space_coords = evaluable.Transpose(space_coords, numpy.argsort(where)) where = tuple(sorted(where)) - chain = args.transform_chains[self._sample.space][0] - index, tail = chain.index_with_tail_in(self._sample.transforms[0]) - coords = tail.apply(space_coords) + (chain, *_), tip_index = args.transform_chains[self._sample.space] + index = evaluable.TransformsRelativeIndex(self._sample.transforms[0], chain, tip_index) + coords = evaluable.TransformsRelativeCoords(self._sample.transforms[0], chain, tip_index, space_coords) expect = self._sample.points.get_evaluable_coords(index) sampled = evaluable.Sampled(coords, expect) indices = self._sample.get_evaluable_indices(index) diff --git a/nutils/topology.py b/nutils/topology.py index 396a8c0da..c260b2fa7 100644 --- a/nutils/topology.py +++ b/nutils/topology.py @@ -1418,8 +1418,7 @@ def trim(self, levelset, maxrefine, ndivisions=8, name='trimmed', leveltopo=None if leveltopo is None: ielem_arg = evaluable.Argument('_trim_index', (), dtype=int) coordinates = self.references.getpoints('vertex', maxrefine).get_evaluable_coords(ielem_arg) - transform_chains = self.transforms.get_evaluable(ielem_arg), self.opposites.get_evaluable(ielem_arg) - levelset = levelset.lower(function.LowerArgs.for_space(self.space, transform_chains, coordinates)).optimized_for_numpy + levelset = levelset.lower(function.LowerArgs.for_space(self.space, (self.transforms, self.opposites), ielem_arg, coordinates)).optimized_for_numpy with log.iter.percentage('trimming', range(len(self)), self.references) as items: for ielem, ref in items: levels = levelset.eval(_trim_index=ielem, **arguments) @@ -1427,8 +1426,8 @@ def trim(self, levelset, maxrefine, ndivisions=8, name='trimmed', leveltopo=None else: log.info('collecting leveltopo elements') coordinates = evaluable.Points(evaluable.NPoints(), self.ndims) - transform_chain = transform.EvaluableTransformChain.from_argument('trans', self.transforms.todims, self.transforms.fromdims) - levelset = levelset.lower(function.LowerArgs.for_space(self.space, (transform_chain, transform_chain), coordinates)).optimized_for_numpy + ielem = evaluable.Argument('_leveltopo_ielem', (), int) + levelset = levelset.lower(function.LowerArgs.for_space(self.space, (leveltopo.transforms, leveltopo.opposites), ielem, coordinates)).optimized_for_numpy bins = [set() for ielem in range(len(self))] for trans in leveltopo.transforms: ielem, tail = self.transforms.index_with_tail(trans) @@ -1443,7 +1442,8 @@ def trim(self, levelset, maxrefine, ndivisions=8, name='trimmed', leveltopo=None while mask.any(): imax = numpy.argmax([mask[indices].sum() for tail, points, indices in cover]) tail, points, indices = cover.pop(imax) - levels[indices] = levelset.eval(trans=trans + tail, _points=points, **arguments) + ielem = leveltopo.transforms.index(trans + tail) + levels[indices] = levelset.eval(_leveltopo_ielem=ielem, _points=points, **arguments) mask[indices] = False refs.append(ref.trim(levels, maxrefine=maxrefine, ndivisions=ndivisions)) log.debug('cache', fcache.stats) @@ -1496,8 +1496,7 @@ def locate(self, geom, coords, *, tol=0, eps=0, maxiter=0, arguments=None, weigh points = parallel.shempty((len(coords), len(geom)), dtype=float) _ielem = evaluable.Argument('_locate_ielem', shape=(), dtype=int) _point = evaluable.Argument('_locate_point', shape=(self.ndims,)) - transform_chains = self.transforms.get_evaluable(_ielem), self.opposites.get_evaluable(_ielem) - egeom = geom.lower(function.LowerArgs.for_space(self.space, transform_chains, _point)) + egeom = geom.lower(function.LowerArgs.for_space(self.space, (self.transforms, self.opposites), _ielem, _point)) xJ = evaluable.Tuple((egeom, evaluable.derivative(egeom, _point))).simplified if skip_missing: if weights is not None: diff --git a/tests/test_function.py b/tests/test_function.py index 539a84a12..762d7f8dd 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -397,14 +397,11 @@ class Custom(TestCase): def assertEvalAlmostEqual(self, factual, fdesired, **args): with self.subTest('0d-points'): self.assertAllAlmostEqual(factual.as_evaluable_array.eval(**args), fdesired.as_evaluable_array.eval(**args)) - transform_chains = dict(test=(transform.EvaluableTransformChain.from_argument('test', 2, 2),)*2) with self.subTest('1d-points'): - coords = evaluable.Zeros((5, 2), float) - lower_args = function.LowerArgs(coords.shape[:-1], transform_chains, dict(test=coords)) + lower_args = function.LowerArgs((evaluable.asarray(5),), {}, {}) self.assertAllAlmostEqual(factual.lower(lower_args).eval(**args), fdesired.lower(lower_args).eval(**args)) with self.subTest('2d-points'): - coords = evaluable.Zeros((5, 6, 2), float) - lower_args = function.LowerArgs(coords.shape[:-1], transform_chains, dict(test=coords)) + lower_args = function.LowerArgs((evaluable.asarray(5), evaluable.asarray(6)), {}, {}) self.assertAllAlmostEqual(factual.lower(lower_args).eval(**args), fdesired.lower(lower_args).eval(**args)) def assertMultipy(self, leftval, rightval): @@ -640,6 +637,7 @@ def setUp(self): numpy.random.seed(0) self.f = basis.dot(numpy.random.uniform(size=len(basis))) sample = self.domain.sample('gauss', 2) + print(sample.eval(self.f)) self.f_sampled = sample.asfunction(sample.eval(self.f)) def test_isarray(self): @@ -1154,7 +1152,8 @@ def test_lower(self): ref = element.PointReference() if self.basis.coords.shape[0] == 0 else element.LineReference()**self.basis.coords.shape[0] points = ref.getpoints('bezier', 4) coordinates = evaluable.Constant(points.coords) - lowered = self.basis.lower(function.LowerArgs(coordinates.shape[:-1], dict(X=(self.checktransforms.get_evaluable(evaluable.Argument('ielem', (), int)),)*2), dict(X=coordinates))) + lowerargs = function.LowerArgs.for_space('X', (self.checktransforms,), evaluable.Argument('ielem', (), int), coordinates) + lowered = self.basis.lower(lowerargs) with _builtin_warnings.catch_warnings(): _builtin_warnings.simplefilter('ignore', category=evaluable.ExpensiveEvaluationWarning) for ielem in range(self.checknelems): diff --git a/tests/test_sample.py b/tests/test_sample.py index 395249b75..7dfbcf1eb 100644 --- a/tests/test_sample.py +++ b/tests/test_sample.py @@ -64,7 +64,8 @@ def test_get_lower_args(self): self.assertEqual(actual_shape, desired_shape) offset = 0 for space, desired_chain, desired_point in zip(self.desired_spaces, desired_chains, desired_points): - self.assertEqual(args.transform_chains[space][0].eval(ielem=ielem), desired_chain) + (chain, *_), index = args.transform_chains[space] + self.assertEqual(chain[index.eval(ielem=ielem).__index__()], desired_chain) desired_coords = desired_point.coords desired_coords = numpy.lib.stride_tricks.as_strided(desired_coords, shape=(*desired_shape, desired_point.ndims,), strides=(0,)*offset+desired_coords.strides[:-1]+(0,)*(len(args.points_shape)-offset-desired_coords.ndim+1)+desired_coords.strides[-1:]) actual_coords = args.coordinates[space].eval(ielem=ielem) @@ -128,7 +129,8 @@ def test_take_elements_single(self): self.assertEqual(take.ndims, self.desired_ndims) args = take.get_lower_args(evaluable.Argument('ielem', (), int)) for space, desired_chain in zip(self.desired_spaces, self.desired_transform_chains[ielem]): - self.assertEqual(args.transform_chains[space][0].eval(ielem=0), desired_chain) + (chain, *_), index = args.transform_chains[space] + self.assertEqual(chain[index.eval(ielem=0).__index__()], desired_chain) def test_take_elements_empty(self): take = self.sample.take_elements(numpy.array([], int)) diff --git a/tests/test_topology.py b/tests/test_topology.py index 847234204..a8da8fbf1 100644 --- a/tests/test_topology.py +++ b/tests/test_topology.py @@ -643,8 +643,9 @@ def assertConnectivity(self, domain, geom): bmask = numpy.zeros(len(boundary), dtype=int) imask = numpy.zeros(len(interfaces), dtype=int) coordinates = evaluable.Points(evaluable.NPoints(), boundary.ndims) - transform_chain = transform.EvaluableTransformChain.from_argument('trans', domain.transforms.todims, boundary.ndims) - lowered_geom = geom.lower(function.LowerArgs.for_space(domain.space, (transform_chain, transform_chain), coordinates)).simplified + edges = domain.transforms.edges(domain.references) + iedge = evaluable.Argument('_iedge', (), int) + lowered_geom = geom.lower(function.LowerArgs.for_space(domain.space, (edges,), iedge, coordinates)).simplified for ielem, ioppelems in enumerate(domain.connectivity): for iedge, ioppelem in enumerate(ioppelems): etrans, eref = domain.references[ielem].edges[iedge] @@ -666,8 +667,8 @@ def assertConnectivity(self, domain, geom): imask[index] += 1 self.assertEqual(eref, opperef) points = eref.getpoints('gauss', 2) - a0 = lowered_geom.eval(trans=trans, _points=points) - a1 = lowered_geom.eval(trans=opptrans, _points=points) + a0 = lowered_geom.eval(_iedge=edges.index(trans), _points=points) + a1 = lowered_geom.eval(_iedge=edges.index(opptrans), _points=points) numpy.testing.assert_array_almost_equal(a0, a1) self.assertTrue(numpy.equal(bmask, 1).all()) self.assertTrue(numpy.equal(imask, 2).all()) From d900bb068e3d279e8dd54f17fe64e11697651630 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Wed, 6 Jul 2022 16:35:23 +0200 Subject: [PATCH 38/45] remove unused EvaluableTransformChain --- nutils/function.py | 1 - nutils/sample.py | 1 - nutils/transform.py | 243 +------------------------------------ nutils/transformseq.py | 132 +------------------- tests/test_transform.py | 76 ------------ tests/test_transformseq.py | 24 ---- 6 files changed, 3 insertions(+), 474 deletions(-) diff --git a/nutils/function.py b/nutils/function.py index 819925782..5a7e0e9fe 100644 --- a/nutils/function.py +++ b/nutils/function.py @@ -6,7 +6,6 @@ from typing import Tuple, Union, Type, Callable, Sequence, Any, Optional, Iterator, Iterable, Dict, Mapping, List, FrozenSet, NamedTuple from . import evaluable, numeric, util, types, warnings, debug_flags, sparse -from .transform import EvaluableTransformChain from .transformseq import Transforms import builtins import numpy diff --git a/nutils/sample.py b/nutils/sample.py index 8dfd2c91f..85cf575b0 100644 --- a/nutils/sample.py +++ b/nutils/sample.py @@ -18,7 +18,6 @@ from . import types, points, util, function, evaluable, parallel, numeric, matrix, sparse, warnings from .pointsseq import PointsSequence from .transformseq import Transforms -from .transform import EvaluableTransformChain from typing import Iterable, Mapping, Optional, Sequence, Tuple, Union import numpy import numbers diff --git a/nutils/transform.py b/nutils/transform.py index d4c8d17f3..928b479fc 100644 --- a/nutils/transform.py +++ b/nutils/transform.py @@ -3,8 +3,7 @@ """ from typing import Tuple, Dict -from . import cache, numeric, util, types, evaluable -from .evaluable import Evaluable, Array +from . import cache, numeric, util, types import numpy import collections import itertools @@ -471,244 +470,4 @@ class Point(Matrix): def __init__(self, offset: types.arraydata): super().__init__(numpy.zeros((offset.shape[0], 0)), offset) -# EVALUABLE TRANSFORM CHAIN - - -class EvaluableTransformChain(Evaluable): - '''The :class:`~nutils.evaluable.Evaluable` equivalent of a transform chain. - - Attributes - ---------- - todims : :class:`int` - The to dimension of the transform chain. - fromdims : :class:`int` - The from dimension of the transform chain. - ''' - - __slots__ = 'todims', 'fromdims' - - @staticmethod - def empty(__dim: int) -> 'EvaluableTransformChain': - '''Return an empty evaluable transform chain with the given dimension. - - Parameters - ---------- - dim : :class:`int` - The to and from dimensions of the empty transform chain. - - Returns - ------- - :class:`EvaluableTransformChain` - The empty evaluable transform chain. - ''' - - return _EmptyTransformChain(__dim) - - @staticmethod - def from_argument(name: str, todims: int, fromdims: int) -> 'EvaluableTransformChain': - '''Return an evaluable transform chain that evaluates to the given argument. - - Parameters - ---------- - name : :class:`str` - The name of the argument. - todims : :class:`int` - The to dimension of the transform chain. - fromdims: :class:`int` - The from dimension of the transform chain. - - Returns - ------- - :class:`EvaluableTransformChain` - The transform chain that evaluates to the given argument. - ''' - - return _TransformChainArgument(name, todims, fromdims) - - def __init__(self, args: Tuple[Evaluable, ...], todims: int, fromdims: int) -> None: - if fromdims > todims: - raise ValueError('The dimension of the tip cannot be larger than the dimension of the root.') - self.todims = todims - self.fromdims = fromdims - super().__init__(args) - - @property - def linear(self) -> Array: - ':class:`nutils.evaluable.Array`: The linear transformation matrix of the entire transform chain. Shape ``(todims,fromdims)``.' - - return _Linear(self) - - @property - def basis(self) -> Array: - ':class:`nutils.evaluable.Array`: A basis for the root coordinate system such that the first :attr:`fromdims` vectors span the tangent space. Shape ``(todims,todims)``.' - - if self.fromdims == self.todims: - return evaluable.diagonalize(evaluable.ones((self.todims,))) - else: - return _Basis(self) - - def apply(self, __coords: Array) -> Array: - '''Apply this transform chain to the last axis given coordinates. - - Parameters - ---------- - coords : :class:`nutils.evaluable.Array` - The coordinates to transform with shape ``(...,fromdims)``. - - Returns - ------- - :class:`nutils.evaluable.Array` - The transformed coordinates with shape ``(...,todims)``. - ''' - - return _Apply(self, __coords) - - def index_with_tail_in(self, __sequence: 'Transforms') -> Tuple[Array, 'EvaluableTransformChain']: - '''Return the evaluable index of this transform chain in the given sequence. - - Parameters - ---------- - sequence : :class:`nutils.transformseq.Transforms` - The sequence of transform chains. - - Returns - ------- - :class:`nutils.evaluable.Array` - The index of this transform chain in the given sequence. - :class:`EvaluableTransformChain` - The tail. - - See also - -------- - :meth:`nutils.transformseq.Transforms.index_with_tail` : the unevaluable version of this method - ''' - - index_tail = _EvaluableIndexWithTail(__sequence, self) - index = evaluable.ArrayFromTuple(index_tail, 0, (), int, _lower=0, _upper=len(__sequence) - 1) - tails = _EvaluableTransformChainFromTuple(index_tail, 1, __sequence.fromdims, self.fromdims) - return index, tails - - -class _Linear(Array): - - __slots__ = '_fromdims' - - def __init__(self, chain: EvaluableTransformChain) -> None: - self._fromdims = chain.fromdims - super().__init__(args=(chain,), shape=(chain.todims, chain.fromdims), dtype=float) - - def evalf(self, chain: TransformChain) -> numpy.ndarray: - return functools.reduce(lambda r, i: i @ r, (item.linear for item in reversed(chain)), numpy.eye(self._fromdims)) - - def _derivative(self, var: evaluable.DerivativeTargetBase, seen: Dict[Evaluable, Evaluable]) -> Array: - return evaluable.zeros(self.shape + var.shape, dtype=float) - - -class _Basis(Array): - - __slots__ = '_todims', '_fromdims' - - def __init__(self, chain: EvaluableTransformChain) -> None: - self._todims = chain.todims - self._fromdims = chain.fromdims - super().__init__(args=(chain,), shape=(chain.todims, chain.todims), dtype=float) - - def evalf(self, chain: TransformChain) -> numpy.ndarray: - linear = numpy.eye(self._fromdims) - for item in reversed(chain): - linear = item.linear @ linear - assert item.fromdims <= item.todims <= item.fromdims + 1 - if item.todims == item.fromdims + 1: - linear = numpy.concatenate([linear, item.ext[:, _]], axis=1) - assert linear.shape == (self._todims, self._todims) - return linear - - def _derivative(self, var: evaluable.DerivativeTargetBase, seen: Dict[Evaluable, Evaluable]) -> Array: - return evaluable.zeros(self.shape + var.shape, dtype=float) - - -class _Apply(Array): - - __slots__ = '_chain', '_coords' - - def __init__(self, chain: EvaluableTransformChain, coords: Array) -> None: - if coords.ndim == 0: - raise ValueError('expected a coords array with at least one axis but got {}'.format(coords)) - if not evaluable.equalindex(chain.fromdims, coords.shape[-1]): - raise ValueError('the last axis of coords does not match the from dimension of the transform chain') - self._chain = chain - self._coords = coords - super().__init__(args=(chain, coords), shape=(*coords.shape[:-1], chain.todims), dtype=float) - - def evalf(self, chain: TransformChain, coords: numpy.ndarray) -> numpy.ndarray: - return apply(chain, coords) - - def _derivative(self, var: evaluable.DerivativeTargetBase, seen: Dict[Evaluable, Evaluable]) -> Array: - axis = self._coords.ndim - 1 - linear = evaluable.appendaxes(evaluable.prependaxes(self._chain.linear, self._coords.shape[:-1]), var.shape) - dcoords = evaluable.insertaxis(evaluable.derivative(self._coords, var, seen), axis, linear.shape[axis]) - return evaluable.dot(linear, dcoords, axis+1) - - -class _EmptyTransformChain(EvaluableTransformChain): - - __slots__ = () - - def __init__(self, dim: int) -> None: - super().__init__((), dim, dim) - - def evalf(self) -> TransformChain: - return () - - def apply(self, points: Array) -> Array: - return points - - @property - def linear(self): - return evaluable.diagonalize(evaluable.ones((self.todims,))) - - -class _TransformChainArgument(EvaluableTransformChain): - - __slots__ = '_name' - - def __init__(self, name: str, todims: int, fromdims: int) -> None: - self._name = name - super().__init__((evaluable.EVALARGS,), todims, fromdims) - - def evalf(self, evalargs) -> TransformChain: - chain = evalargs[self._name] - assert isinstance(chain, tuple) and all(isinstance(item, TransformItem) for item in chain) - assert not chain or chain[0].todims == self.todims and chain[-1].fromdims == self.fromdims - return chain - - @property - def arguments(self): - return frozenset({self}) - - -class _EvaluableIndexWithTail(evaluable.Evaluable): - - __slots__ = '_sequence' - - def __init__(self, sequence: 'Transforms', chain: EvaluableTransformChain) -> None: - self._sequence = sequence - super().__init__((chain,)) - - def evalf(self, chain: TransformChain) -> Tuple[numpy.ndarray, TransformChain]: - index, tails = self._sequence.index_with_tail(chain) - return numpy.array(index), tails - - -class _EvaluableTransformChainFromTuple(EvaluableTransformChain): - - __slots__ = '_index' - - def __init__(self, items: evaluable.Evaluable, index: int, todims: int, fromdims: int) -> None: - self._index = index - super().__init__((items,), todims, fromdims) - - def evalf(self, items: tuple) -> TransformChain: - return items[self._index] - # vim:sw=2:sts=2:et diff --git a/nutils/transformseq.py b/nutils/transformseq.py index 2ffcc559b..0e1f6fbaa 100644 --- a/nutils/transformseq.py +++ b/nutils/transformseq.py @@ -1,9 +1,9 @@ """The transformseq module.""" from typing import Tuple -from . import types, numeric, util, transform, element, evaluable +from . import types, numeric, util, transform, element from .elementseq import References -from .transform import TransformChain, EvaluableTransformChain +from .transform import TransformChain import abc import itertools import operator @@ -305,22 +305,6 @@ def unchain(self): yield self - def get_evaluable(self, index: evaluable.Array) -> EvaluableTransformChain: - '''Return the evaluable transform chain at the given index. - - Parameters - ---------- - index : a scalar, integer :class:`nutils.evaluable.Array` - The index of the transform chain to return. - - Returns - ------- - :class:`nutils.transform.EvaluableTransformChain` - The evaluable transform chain at the given ``index``. - ''' - - return _EvaluableTransformChainFromSequence(self, index) - stricttransforms = types.strict[Transforms] @@ -433,9 +417,6 @@ def __getitem__(self, index): return super().__getitem__(index) return transform.Index(self.fromdims, self._offset + numeric.normdim(self._length, index.__index__())), - def get_evaluable(self, index: evaluable.Array) -> EvaluableTransformChain: - return _EvaluableIndexChain(self.fromdims, self._offset + evaluable.InRange(index, self._length)) - def __len__(self): return self._length @@ -624,9 +605,6 @@ def index_with_tail(self, trans): return flatindex, tail - def get_evaluable(self, index: evaluable.Array) -> EvaluableTransformChain: - return _EvaluableTransformChainFromStructured(self, index) - class MaskedTransforms(Transforms): '''An order preserving subset of another :class:`Transforms` object. @@ -945,110 +923,4 @@ def chain(items, todims, fromdims): else: return ChainedTransforms(unchained) - -class _EvaluableTransformChainFromSequence(EvaluableTransformChain): - - __slots__ = '_sequence', '_index' - - def __init__(self, sequence: Transforms, index: evaluable.Array) -> None: - self._sequence = sequence - self._index = index - super().__init__((index,), sequence.todims, sequence.fromdims) - - def evalf(self, index: numpy.ndarray) -> TransformChain: - return self._sequence[index.__index__()] - - def index_with_tail_in(self, __sequence) -> Tuple[evaluable.Array, EvaluableTransformChain]: - if __sequence == self._sequence: - tails = EvaluableTransformChain.empty(self._sequence.todims) - return self._index, tails - else: - return super().index_with_tail_in(__sequence) - - -class _EvaluableIndexChain(EvaluableTransformChain): - - __slots__ = '_ndim' - - def __init__(self, ndim: int, index: evaluable.Array) -> None: - self._ndim = ndim - super().__init__((index,), ndim, ndim) - - def evalf(self, index: numpy.ndarray) -> TransformChain: - return transform.Index(self._ndim, index.__index__()), - - def apply(self, points: evaluable.Array) -> evaluable.Array: - return points - - @property - def linear(self) -> evaluable.Array: - return evaluable.diagonalize(evaluable.ones((self.todims,))) - - -class _EvaluableTransformChainFromStructured(EvaluableTransformChain): - - __slots__ = '_sequence', '_index' - - def __init__(self, sequence: StructuredTransforms, index: evaluable.Array) -> None: - self._sequence = sequence - self._index = index - super().__init__((index,), sequence.todims, sequence.fromdims) - - def evalf(self, index: numpy.ndarray) -> TransformChain: - return self._sequence[index.__index__()] - - def apply(self, points: evaluable.Array) -> evaluable.Array: - if len(self._sequence._axes) != 1: - return super().apply(points) - desired = super().apply(points) - axis = self._sequence._axes[0] - # axis.map - index = self._index + axis.i - if axis.mod: - index %= axis.mod - # edge - if axis.isdim: - assert evaluable.equalindex(points.shape[-1], 1) - else: - assert evaluable.equalindex(points.shape[-1], 0) - points = evaluable.appendaxes(float(axis.side), (*points.shape[:-1], 1)) - # children - for i in range(self._sequence._nrefine): - index, ichild = evaluable.divmod(index, 2) - points = .5 * (points + evaluable.appendaxes(ichild, points.shape)) - # shift - return points + evaluable.appendaxes(index, points.shape) - - @property - def linear(self) -> evaluable.Array: - if not len(self._sequence): - return super().linear - chain = self._sequence[0] - linear = numpy.eye(self.fromdims) - for item in reversed(chain): - linear = item.linear @ linear - assert linear.shape == (self.todims, self.fromdims) - return evaluable.asarray(linear) - - @property - def basis(self) -> evaluable.Array: - if not len(self._sequence) or self.fromdims == self.todims: - return super().basis - chain = self._sequence[0] - basis = numpy.eye(self.fromdims) - for item in reversed(chain): - basis = item.linear @ basis - assert item.fromdims <= item.todims <= item.fromdims + 1 - if item.todims == item.fromdims + 1: - basis = numpy.concatenate([basis, item.ext[:, None]], axis=1) - assert basis.shape == (self.todims, self.todims) - return evaluable.asarray(basis) - - def index_with_tail_in(self, __sequence) -> Tuple[evaluable.Array, EvaluableTransformChain]: - if __sequence == self._sequence: - tails = EvaluableTransformChain.empty(self._sequence.todims) - return self._index, tails - else: - return super().index_with_tail_in(__sequence) - # vim:sw=2:sts=2:et diff --git a/tests/test_transform.py b/tests/test_transform.py index 6e05ce6fd..e5dce1082 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -98,79 +98,3 @@ def setUp(self): del TestTransform, TestInvertible, TestUpdim - - -class EvaluableTransformChainArgument(TestCase): - - def test_evalf(self): - chain = transform.SimplexEdge(2, 0), - echain = transform.EvaluableTransformChain.from_argument('chain', 2, 1) - self.assertEqual(echain.eval(chain=chain), chain) - - def test_todims(self): - echain = transform.EvaluableTransformChain.from_argument('chain', 2, 1) - self.assertEqual(echain.todims, 2) - - def test_fromdims(self): - echain = transform.EvaluableTransformChain.from_argument('chain', 2, 1) - self.assertEqual(echain.fromdims, 1) - - def test_linear(self): - chain = transform.SimplexEdge(2, 0), - echain = transform.EvaluableTransformChain.from_argument('chain', 2, 1) - self.assertAllAlmostEqual(echain.linear.eval(chain=chain), numpy.array([[-1.], [1.]])) - - def test_linear_derivative(self): - echain = transform.EvaluableTransformChain.from_argument('chain', 2, 1) - self.assertTrue(evaluable.iszero(evaluable.derivative(echain.linear, evaluable.Argument('test', ())).simplified)) - - def test_basis(self): - chain = transform.SimplexEdge(2, 0), - echain = transform.EvaluableTransformChain.from_argument('chain', 2, 1) - self.assertAllAlmostEqual(echain.basis.eval(chain=chain), numpy.array([[-1., 1.], [1., 1.]])) - - def test_basis_derivative(self): - echain = transform.EvaluableTransformChain.from_argument('chain', 2, 1) - self.assertTrue(evaluable.iszero(evaluable.derivative(echain.basis, evaluable.Argument('test', ())).simplified)) - - def test_apply(self): - chain = transform.SimplexEdge(2, 0), - echain = transform.EvaluableTransformChain.from_argument('chain', 2, 1) - ecoords = evaluable.Argument('coords', (5, echain.fromdims), float) - coords = numpy.linspace(0, 1, 5*echain.fromdims).reshape(5, echain.fromdims) - self.assertAllAlmostEqual(echain.apply(ecoords).eval(chain=chain, coords=coords), transform.apply(chain, coords)) - - def test_apply_derivative(self): - chain = transform.SimplexEdge(2, 0), - echain = transform.EvaluableTransformChain.from_argument('chain', 2, 1) - ecoords = evaluable.Argument('coords', (5, echain.fromdims), float) - coords = numpy.linspace(0, 1, 5*echain.fromdims).reshape(5, echain.fromdims) - actual = evaluable.derivative(echain.apply(ecoords), ecoords).eval(chain=chain) - desired = numpy.einsum('jk,iklm->ijlm', numpy.array([[-1.], [1.]]), numpy.eye(5*echain.fromdims).reshape(5, echain.fromdims, 5, echain.fromdims)) - self.assertAllAlmostEqual(actual, desired) - - -class EmptyEvaluableTransformChain(TestCase): - - def setUp(self): - super().setUp() - self.chain = transform.EvaluableTransformChain.empty(2) - - def test_evalf(self): - self.assertEqual(self.chain.evalf(), ()) - - def test_todims(self): - self.assertEqual(self.chain.todims, 2) - - def test_fromdims(self): - self.assertEqual(self.chain.fromdims, 2) - - def test_linear(self): - self.assertAllAlmostEqual(self.chain.linear.eval(), numpy.diag([1, 1])) - - def test_basis(self): - self.assertAllAlmostEqual(self.chain.basis.eval(), numpy.diag([1, 1])) - - def test_apply(self): - coords = numpy.array([1., 2.]) - self.assertAllAlmostEqual(self.chain.apply(evaluable.Argument('coords', (2,))).eval(coords=coords), coords) diff --git a/tests/test_transformseq.py b/tests/test_transformseq.py index 2266ee549..18d1dfa63 100644 --- a/tests/test_transformseq.py +++ b/tests/test_transformseq.py @@ -144,30 +144,6 @@ def test_refined(self): for i, trans in enumerate(ctransforms): self.assertEqual(refined.index(trans), i) - def test_get_evaluable(self): - eindex = nutils.evaluable.InRange(nutils.evaluable.Argument('index', (), int), len(self.check)) - echain = self.seq.get_evaluable(eindex) - for index, chain in enumerate(self.check): - self.assertEqual(echain.eval(index=index), chain) - - def test_index_with_tail_in(self): - assert len(self.check) == len(self.checkrefs) - echain = nutils.transform.EvaluableTransformChain.from_argument('chain', self.checktodims, self.checkfromdims) - eindex, etail = echain.index_with_tail_in(self.seq) - for i, (trans, ref) in enumerate(zip(self.check, self.checkrefs)): - self.assertEqual(int(eindex.eval(chain=trans)), i) - self.assertEqual(etail.eval(chain=trans), ()) - for ctrans in ref.child_transforms: - self.assertEqual(self.seq.index_with_tail(trans+(ctrans,)), (i, (ctrans,))) - if self.checkfromdims > 0: - echain = nutils.transform.EvaluableTransformChain.from_argument('chain', self.checktodims, self.checkfromdims-1) - eindex, etail = echain.index_with_tail_in(self.seq) - for i, (trans, ref) in enumerate(zip(self.check, self.checkrefs)): - for etrans in ref.edge_transforms: - for shuffle in lambda t: t, nutils.transform.canonical: - self.assertEqual(int(eindex.eval(chain=shuffle(trans+(etrans,)))), i) - self.assertEqual(etail.eval(chain=shuffle(trans+(etrans,))), (etrans,)) - class Edges: From f652f858defa22425b04d98e3644d6785bfc7431 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Thu, 7 Jul 2022 13:50:18 +0200 Subject: [PATCH 39/45] WIP --- src/lib.rs | 165 ++++++++++++++++++- src/map/mod.rs | 5 + src/map/ops.rs | 107 ++++++------ src/map/primitive.rs | 30 ++-- src/map/relative.rs | 25 ++- src/map/tesselation.rs | 103 ++++++++---- src/map/transforms.rs | 360 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 688 insertions(+), 107 deletions(-) create mode 100644 src/map/transforms.rs diff --git a/src/lib.rs b/src/lib.rs index e46e8a3f8..8f616e82d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,10 +3,11 @@ pub mod map; pub mod simplex; mod util; +use map::relative::RelativeTo; use map::tesselation::Tesselation; +use map::transforms::Transforms; use map::Map; -use map::relative::RelativeTo; -use numpy::{IntoPyArray, IxDyn, PyArray, PyArrayDyn, PyReadonlyArrayDyn}; +use numpy::{IntoPyArray, IxDyn, PyArray, PyArrayDyn, PyReadonlyArrayDyn, PyReadonlyArray2}; use pyo3::exceptions::{PyIndexError, PyValueError}; use pyo3::prelude::*; use simplex::Simplex; @@ -79,7 +80,12 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; - fn apply_map_from_numpy<'py>(py: Python<'py>, map: &impl Map, index: usize, coords: PyReadonlyArrayDyn) -> PyResult<(usize, &'py PyArrayDyn)> { + fn apply_map_from_numpy<'py>( + py: Python<'py>, + map: &impl Map, + index: usize, + coords: PyReadonlyArrayDyn, + ) -> PyResult<(usize, &'py PyArrayDyn)> { if coords.ndim() == 0 { return Err(PyValueError::new_err( "the `coords` argument must have at least one dimension", @@ -141,8 +147,8 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { pub fn concat(&self, other: &PyTesselation) -> PyResult { Ok(Self(self.0.concat(&other.0)?)) } - pub fn take(&self, indices: Vec) -> Self { - Self(self.0.take(&indices)) + pub fn take(&self, indices: Vec) -> PyResult { + Ok(Self(self.0.take(&indices)?)) } #[getter] pub fn children(&self) -> Self { @@ -180,7 +186,10 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { // .ok_or(PyValueError::new_err("index out of range")) //} pub fn relative_to(&self, target: &Self) -> PyResult { - self.0.relative_to(&target.0).map(|rel| PyMap(rel)).ok_or(PyValueError::new_err("cannot make relative")) + self.0 + .relative_to(&target.0) + .map(|rel| PyMap(rel)) + .ok_or(PyValueError::new_err("cannot make relative")) } } @@ -232,12 +241,154 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { pub fn unapply_indices(&self, indices: Vec) -> PyResult> { self.0 .unapply_indices(&indices) - .map(|mut indices| { indices.sort(); indices }) + .map(|mut indices| { + indices.sort(); + indices + }) .ok_or(PyValueError::new_err("index out of range")) } } m.add_class::()?; + #[pyclass(name = "Transforms", module = "nutils._rust")] + #[derive(Debug, Clone)] + struct PyTransforms(Transforms); + + #[pymethods] + impl PyTransforms { + #[staticmethod] + pub fn identity(dim: usize, len: usize) -> Self { + Transforms::identity(dim, len).into() + } + pub fn __repr__(&self) -> String { + format!("{:?}", self.0) + } + pub fn __len__(&self) -> usize { + self.0.len_in() + } + #[getter] + pub fn fromlen(&self) -> usize { + self.0.len_out() + } + #[getter] + pub fn tolen(&self) -> usize { + self.0.len_out() + } + #[getter] + pub fn fromdims(&self) -> usize { + self.0.dim_in() + } + #[getter] + pub fn todims(&self) -> usize { + self.0.dim_out() + } + pub fn __mul__(&self, rhs: &PyTransforms) -> Self { + Self(&self.0 * &rhs.0) + } + pub fn concat(&self, other: &PyTransforms) -> PyResult { + Ok(Self(self.0.concat(&other.0)?)) + } + pub fn take(&self, indices: Vec) -> PyResult { + Ok(Self(self.0.take(&indices)?)) + } + pub fn children(&self, simplex: &PySimplex, offset: usize) -> PyResult { + Ok(Self(self.0.children(simplex.into(), offset)?)) + } + pub fn edges(&self, simplex: &PySimplex, offset: usize) -> PyResult { + Ok(Self(self.0.edges(simplex.into(), offset)?)) + } + pub fn uniform_points( + &self, + points: PyReadonlyArray2, + offset: usize, + ) -> PyResult { + let point_dim = points.shape()[1]; + let points: Vec = points.as_array().iter().cloned().collect(); + Ok(Self(self.0.uniform_points(points, point_dim, offset)?)) + } + pub fn apply_index(&self, index: usize) -> PyResult { + self.0 + .apply_index(index) + .ok_or(PyIndexError::new_err("index out of range")) + } + pub fn apply<'py>( + &self, + py: Python<'py>, + index: usize, + coords: PyReadonlyArrayDyn, + ) -> PyResult<(usize, &'py PyArrayDyn)> { + apply_map_from_numpy(py, &self.0, index, coords) + } + //pub fn unapply_indices(&self, indices: Vec) -> PyResult> { + // self.0 + // .unapply_indices(&indices) + // .map(|mut indices| { indices.sort(); indices }) + // .ok_or(PyValueError::new_err("index out of range")) + //} + pub fn relative_to(&self, target: &Self) -> PyResult { + self.0 + .relative_to(&target.0) + .map(|rel| PyRelativeTransforms(rel)) + .ok_or(PyValueError::new_err("cannot make relative")) + } + } + + impl From for PyTransforms { + fn from(transforms: Transforms) -> PyTransforms { + PyTransforms(transforms) + } + } + + #[pyclass(name = "RelativeTransforms", module = "nutils._rust")] + #[derive(Debug, Clone)] + struct PyRelativeTransforms(>::Output); + + #[pymethods] + impl PyRelativeTransforms { + pub fn __repr__(&self) -> String { + format!("{:?}", self.0) + } + pub fn len_out(&self) -> usize { + self.0.len_out() + } + pub fn len_in(&self) -> usize { + self.0.len_in() + } + pub fn dim_out(&self) -> usize { + self.0.dim_out() + } + pub fn dim_in(&self) -> usize { + self.0.dim_in() + } + pub fn delta_dim(&self) -> usize { + self.0.delta_dim() + } + pub fn apply_index(&self, index: usize) -> PyResult { + self.0 + .apply_index(index) + .ok_or(PyIndexError::new_err("index out of range")) + } + pub fn apply<'py>( + &self, + py: Python<'py>, + index: usize, + coords: PyReadonlyArrayDyn, + ) -> PyResult<(usize, &'py PyArrayDyn)> { + apply_map_from_numpy(py, &self.0, index, coords) + } + pub fn unapply_indices(&self, indices: Vec) -> PyResult> { + self.0 + .unapply_indices(&indices) + .map(|mut indices| { + indices.sort(); + indices + }) + .ok_or(PyValueError::new_err("index out of range")) + } + } + + m.add_class::()?; + Ok(()) } diff --git a/src/map/mod.rs b/src/map/mod.rs index 85191d7fe..eb3db31db 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -2,6 +2,7 @@ pub mod ops; pub mod primitive; pub mod relative; pub mod tesselation; +pub mod transforms; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { @@ -11,6 +12,7 @@ pub enum Error { DimensionZeroHasNoEdges, StrideSmallerThanOutputDimension, IndexOutOfRange, + IndicesNotStrictIncreasing, } impl std::error::Error for Error {} @@ -27,6 +29,9 @@ impl std::fmt::Display for Error { "The stride of the `coords` argument is smaller than the output dimension." ), Self::IndexOutOfRange => write!(f, "The index is out of range."), + Self::IndicesNotStrictIncreasing => { + write!(f, "The indices are not strict monotonic increasing.") + } } } } diff --git a/src/map/ops.rs b/src/map/ops.rs index c6e261815..c30a55e32 100644 --- a/src/map/ops.rs +++ b/src/map/ops.rs @@ -306,10 +306,17 @@ impl Map for BinaryConcat { /// The concatenation of an unempty sequence of maps. #[derive(Debug, Clone, PartialEq)] -pub struct UniformConcat>(Array) +pub struct UniformConcat> where M: Map, - Array: Deref; + Array: Deref, +{ + maps: Array, + dim_in: usize, + delta_dim: usize, + len_out: usize, + len_in: usize, +} impl UniformConcat where @@ -318,33 +325,37 @@ where { /// Returns the concatenation of an unempty sequence of maps. /// - /// The two maps must have the same input and output dimensions and the - /// same output length. The maps must not overlap. + /// The maps must not overlap. /// /// Returns an [`Error`] if the dimensions and lengths don't match. - pub fn new(array: Array) -> Result { - let mut iter = array.iter(); - if let Some(map) = iter.next() { - let dim_out = map.dim_out(); - let dim_in = map.dim_in(); - let len_out = map.len_out(); - for map in iter { - if map.dim_in() != dim_in || map.dim_out() != dim_out { - return Err(Error::DimensionMismatch); - } else if map.len_out() != len_out { - return Err(Error::LengthMismatch); - } + pub fn new( + maps: Array, + dim_in: usize, + delta_dim: usize, + len_out: usize, + ) -> Result { + let mut len_in = 0; + for map in maps.iter() { + len_in += map.len_in(); + if map.dim_in() != dim_in || map.delta_dim() != delta_dim { + return Err(Error::DimensionMismatch); + } else if map.len_out() != len_out { + return Err(Error::LengthMismatch); } - Ok(Self(array)) - } else { - Err(Error::Empty) } + Ok(Self { + maps, + dim_in, + delta_dim, + len_out, + len_in, + }) } - pub fn new_unchecked(array: Array) -> Self { - Self(array) + pub fn new_unchecked(maps: Array, dim_out: usize, dim_in: usize, len_out: usize) -> Self { + Self::new(maps, dim_out, dim_in, len_out).unwrap() } pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, M> { - self.0.iter() + self.maps.iter() } } @@ -355,23 +366,19 @@ where { #[inline] fn dim_in(&self) -> usize { - self.0.first().unwrap().dim_in() - } - #[inline] - fn dim_out(&self) -> usize { - self.0.first().unwrap().dim_out() + self.dim_in } #[inline] fn delta_dim(&self) -> usize { - self.0.first().unwrap().delta_dim() + self.delta_dim } #[inline] fn len_in(&self) -> usize { - self.iter().map(|map| map.len_in()).sum() + self.len_in } #[inline] fn len_out(&self) -> usize { - self.0.first().unwrap().len_out() + self.len_out } #[inline] fn apply_inplace_unchecked( @@ -402,18 +409,21 @@ where #[inline] fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec { let mut iter = self.iter(); - let map = iter.next().unwrap(); - let mut result = map.unapply_indices_unchecked(indices); - let mut offset = map.len_in(); - for map in iter { - result.extend( - map.unapply_indices_unchecked(indices) - .into_iter() - .map(|i| i.set(i.get() + offset)), - ); - offset += map.len_in(); + if let Some(map) = iter.next() { + let mut result = map.unapply_indices_unchecked(indices); + let mut offset = map.len_in(); + for map in iter { + result.extend( + map.unapply_indices_unchecked(indices) + .into_iter() + .map(|i| i.set(i.get() + offset)), + ); + offset += map.len_in(); + } + result + } else { + Vec::new() } - result } #[inline] fn is_identity(&self) -> bool { @@ -800,11 +810,16 @@ mod tests { #[test] fn uniform_concat() { - let map = UniformConcat::new(vec![ - prim_comp![Line*3 <- Take([0], 3)], - prim_comp![Line*3 <- Take([1], 3) <- Children], - prim_comp![Line*3 <- Take([2], 3) <- Children <- Children], - ]) + let map = UniformConcat::new( + vec![ + prim_comp![Line*3 <- Take([0], 3)], + prim_comp![Line*3 <- Take([1], 3) <- Children], + prim_comp![Line*3 <- Take([2], 3) <- Children <- Children], + ], + 1, + 0, + 3, + ) .unwrap(); assert_eq!(map.len_out(), 3); assert_eq!(map.len_in(), 7); diff --git a/src/map/primitive.rs b/src/map/primitive.rs index c5ddb0d14..2c4d21ad1 100644 --- a/src/map/primitive.rs +++ b/src/map/primitive.rs @@ -1061,20 +1061,24 @@ pub trait AllPrimitiveDecompositions: Sized { if self.is_identity() { return Some(Vec::new()); } - let mut next = |m: &Self| if let Some(((Primitive::Transpose(transpose), stride), rhs)) = m.all_primitive_decompositions().next() { - let len = transpose.mod_out(); - if stride != 1 { - unimplemented!{} - transposes.push(Transpose::new(stride, len)); - } - transposes.push(transpose); - if stride != 1 { - unimplemented!{} - transposes.push(Transpose::new(len, stride)); + let mut next = |m: &Self| { + if let Some(((Primitive::Transpose(transpose), stride), rhs)) = + m.all_primitive_decompositions().next() + { + let len = transpose.mod_out(); + if stride != 1 { + unimplemented! {} + transposes.push(Transpose::new(stride, len)); + } + transposes.push(transpose); + if stride != 1 { + unimplemented! {} + transposes.push(Transpose::new(len, stride)); + } + Some(rhs) + } else { + None } - Some(rhs) - } else { - None }; let mut rhs = if let Some(rhs) = next(self) { rhs diff --git a/src/map/relative.rs b/src/map/relative.rs index b90a3b10f..b598118e4 100644 --- a/src/map/relative.rs +++ b/src/map/relative.rs @@ -3,7 +3,7 @@ use super::ops::{ }; use super::primitive::{ AllPrimitiveDecompositions, Identity, Primitive, PrimitiveDecompositionIter, Slice, - SwapPrimitiveComposition, UnboundedMap, WithBounds, Transpose, + SwapPrimitiveComposition, Transpose, UnboundedMap, WithBounds, }; use super::{AddOffset, Error, Map, UnapplyIndicesData}; use crate::util::ReplaceNthIter as _; @@ -136,7 +136,7 @@ where .next() { if stride != 1 && outer.mod_out() != 1 { - common.push(Primitive::new_transpose(stride, outer_mod_out)); + common.push(Primitive::new_transpose(stride, outer.mod_out())); } common.push(outer); (map1, map2) @@ -160,7 +160,9 @@ where PartialRelative::All(rel, None) } else if let Some(mut transposes) = rem.as_transposes() { transposes.reverse(); - transposes.iter_mut().for_each(|transpose| transpose.reverse()); + transposes + .iter_mut() + .for_each(|transpose| transpose.reverse()); let transposes = WithBounds::new_unchecked(transposes, rem.dim_in(), rem.len_in()); PartialRelative::All(rel, Some(transposes)) } else if rem.is_index_map() { @@ -264,9 +266,13 @@ impl RelativeTo for WithBounds> { Some(Relative::Map(rel)) } else if let Some(mut transposes) = rem.as_transposes() { transposes.reverse(); - transposes.iter_mut().for_each(|transpose| transpose.reverse()); + transposes + .iter_mut() + .for_each(|transpose| transpose.reverse()); let transposes = WithBounds::new_unchecked(transposes, rem.dim_in(), rem.len_in()); - Some(Relative::TransposedMap(BinaryComposition::new_unchecked(transposes, rel))) + Some(Relative::TransposedMap(BinaryComposition::new_unchecked( + transposes, rel, + ))) } else { None } @@ -284,7 +290,14 @@ where self.iter() .map(|item| item.relative_to(target)) .collect::>() - .map(|rels| UniformConcat::new_unchecked(rels)) + .map(|rels| { + UniformConcat::new_unchecked( + rels, + self.dim_in(), + target.dim_in() - self.dim_in(), + target.len_in(), + ) + }) } } diff --git a/src/map/tesselation.rs b/src/map/tesselation.rs index 287872c48..a6b866a5b 100644 --- a/src/map/tesselation.rs +++ b/src/map/tesselation.rs @@ -184,23 +184,17 @@ impl RelativeTo for UniformTesselation { pub struct Tesselation(UniformConcat); impl Tesselation { - pub fn new(items: Vec) -> Result { - let items = items.into_iter().filter(|map| map.len_in() > 0).collect(); - Ok(Self(UniformConcat::new(items)?)) - } - pub fn concat_iter(items: impl Iterator) -> Result { - let mut maps = Vec::new(); - for item in items { - if item.len() > 0 { - maps.extend(item.0.iter().cloned()); - } - } - Self::new(maps) - } pub fn identity(shapes: Vec, len: usize) -> Self { - Self(UniformConcat::new_unchecked(vec![ - UniformTesselation::identity(shapes, len), - ])) + let identity = UniformTesselation::identity(shapes, len); + let dim_in = identity.dim_in(); + let delta_dim = identity.delta_dim(); + let len_out = identity.len_out(); + Self(UniformConcat::new_unchecked( + vec![identity], + dim_in, + delta_dim, + len_out, + )) } pub fn len(&self) -> usize { self.0.len_in() @@ -213,28 +207,51 @@ impl Tesselation { } pub fn concat(&self, other: &Self) -> Result { let maps = self.0.iter().chain(other.0.iter()).cloned().collect(); - Ok(Self(UniformConcat::new(maps)?)) - } - pub fn take(&self, indices: &[usize]) -> Self { - assert!(indices.windows(2).all(|pair| pair[0] < pair[1])); + Ok(Self(UniformConcat::new( + maps, + self.dim_in(), + self.delta_dim(), + self.len_out(), + )?)) + } + pub fn take(&self, indices: &[usize]) -> Result { + if !indices.windows(2).all(|pair| pair[0] < pair[1]) { + return Err(Error::IndicesNotStrictIncreasing); + } if let Some(last) = indices.last() { - assert!(*last < self.len()); + if *last >= self.len() { + return Err(Error::IndexOutOfRange); + } + } + let mut maps = Vec::new(); + let mut offset = 0; + let mut start = 0; + for map in self.0.iter() { + let stop = start + indices[start..].partition_point(|i| *i < offset + map.len_in()); + if stop > start { + let map_indices: Vec<_> = + indices[start..stop].iter().map(|i| *i - offset).collect(); + start = stop; + maps.push(map.take(&map_indices)); + } + offset += map.len_in(); } - // TODO: Make sure that `UniformConcat` accepts an empty vector. - let maps = self.0.iter().scan((0, 0), |(start, offset), map| { - let stop = *start + indices[*start..].partition_point(|i| *i < *offset + map.len_in()); - let map_indices: Vec<_> = indices[*start..stop].iter().map(|i| *i - *offset).collect(); - *start = stop; - *offset += map.len_in(); - Some(map.take(&map_indices)) - }); - let result = Self(UniformConcat::new_unchecked(maps.collect())); - assert_eq!(result.len(), indices.len()); - result + assert_eq!(start, indices.len()); + Ok(Self(UniformConcat::new_unchecked( + maps, + self.dim_in(), + self.delta_dim(), + self.len_out(), + ))) } pub fn children(&self) -> Self { let maps = self.0.iter().map(|item| item.children()).collect(); - Self(UniformConcat::new_unchecked(maps)) + Self(UniformConcat::new_unchecked( + maps, + self.dim_in(), + self.delta_dim(), + self.len_out(), + )) } pub fn edges(&self) -> Result { if self.dim_in() == 0 { @@ -245,16 +262,27 @@ impl Tesselation { .iter() .flat_map(|item| item.edges_iter().unwrap()) .collect(); - Ok(Self(UniformConcat::new(items)?)) + Ok(Self(UniformConcat::new( + items, + self.dim_in() - 1, + self.delta_dim() + 1, + self.len_out(), + )?)) } pub fn centroids(&self) -> Self { Self(UniformConcat::new_unchecked( self.0.iter().map(|item| item.centroids()).collect(), + 0, + self.delta_dim() + self.dim_in(), + self.len_out(), )) } pub fn vertices(&self) -> Self { Self(UniformConcat::new_unchecked( self.0.iter().map(|item| item.vertices()).collect(), + 0, + self.delta_dim() + self.dim_in(), + self.len_out(), )) } pub fn apply_inplace( @@ -372,7 +400,12 @@ impl Mul for &Tesselation { .iter() .flat_map(|lhs| rhs.0.iter().map(move |rhs| lhs * rhs)) .collect(); - Tesselation(UniformConcat::new_unchecked(products)) + Tesselation(UniformConcat::new_unchecked( + products, + self.dim_in() + rhs.dim_in(), + self.delta_dim() + rhs.delta_dim(), + self.len_out() * rhs.len_out(), + )) } } diff --git a/src/map/transforms.rs b/src/map/transforms.rs new file mode 100644 index 000000000..429cc6c1b --- /dev/null +++ b/src/map/transforms.rs @@ -0,0 +1,360 @@ +use super::ops::UniformConcat; +use super::primitive::{ + AllPrimitiveDecompositions, Primitive, PrimitiveDecompositionIter, UnboundedMap, WithBounds, +}; +use super::relative::RelativeTo; +use super::{AddOffset, Error, Map, UnapplyIndicesData}; +use crate::simplex::Simplex; +use crate::util::{ReplaceNthIter, SkipNthIter}; +use std::iter; +use std::ops::Mul; +use std::sync::Arc; + +#[derive(Debug, Clone, PartialEq)] +pub struct UniformTransforms(WithBounds>); + +impl UniformTransforms { + pub fn identity(dim: usize, len: usize) -> Self { + Self(WithBounds::new_unchecked(Vec::new(), dim, len)) + } + pub fn clone_and_push(&self, primitive: Primitive) -> Result { + if self.0.dim_in() < primitive.dim_out() { + return Err(Error::DimensionMismatch); + } + if self.0.len_in() % primitive.mod_out() != 0 { + return Err(Error::LengthMismatch); + } + let dim_in = self.0.dim_in() - primitive.delta_dim(); + let len_in = self.0.len_in() / primitive.mod_out() * primitive.mod_in(); + let map = self + .0 + .unbounded() + .iter() + .cloned() + .chain(iter::once(primitive)) + .collect(); + Ok(Self(WithBounds::new_unchecked(map, dim_in, len_in))) + } + fn mul(&self, rhs: &Self) -> Self { + let offset = self.0.dim_in(); + let map = iter::once(Primitive::new_transpose(rhs.0.len_out(), self.0.len_out())) + .chain(self.0.unbounded().iter().cloned()) + .chain(iter::once(Primitive::new_transpose( + self.0.len_in(), + rhs.0.len_out(), + ))) + .chain(rhs.0.unbounded().iter().map(|item| { + let mut item = item.clone(); + item.add_offset(offset); + item + })) + .collect(); + Self(WithBounds::new_unchecked( + map, + self.0.dim_in() + rhs.0.dim_in(), + self.0.len_in() * rhs.0.len_in(), + )) + } +} + +//impl Deref for UniformTransforms { +// type Target = WithBounds>; +// +// fn deref(&self) -> Self::Target { +// self.map +// } +//} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $(-> $ret:ty)? + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { + $self.map.$fn($($arg),*) + } + }; +} + +impl Map for UniformTransforms { + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Result} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} + dispatch! {fn is_index_map(&self) -> bool} +} + +impl AllPrimitiveDecompositions for UniformTransforms { + fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { + Box::new( + self.0 + .all_primitive_decompositions() + .map(|(prim, map)| (prim, Self(map))), + ) + } +} + +impl RelativeTo for UniformTransforms { + type Output = > as RelativeTo>>>::Output; + + fn relative_to(&self, target: &Self) -> Option { + self.0.relative_to(&target.0) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Transforms(UniformConcat); + +impl Transforms { + pub fn identity(dim: usize, len: usize) -> Self { + let identity = UniformTransforms::identity(dim, len); + Self(UniformConcat::new_unchecked(vec![identity], dim, 0, len)) + } + fn mul(&self, rhs: &Self) -> Self { + let products = self + .0 + .iter() + .flat_map(|lhs| rhs.0.iter().map(move |rhs| lhs * rhs)) + .collect(); + Transforms(UniformConcat::new_unchecked( + products, + self.dim_in() + rhs.dim_in(), + self.delta_dim() + rhs.delta_dim(), + self.len_out() * rhs.len_out(), + )) + } + pub fn concat(&self, other: &Self) -> Result { + let maps = self.0.iter().chain(other.0.iter()).cloned().collect(); + Ok(Self(UniformConcat::new( + maps, + self.dim_in(), + self.delta_dim(), + self.len_out(), + )?)) + } + pub fn take(&self, indices: &[usize]) -> Result { + if !indices.windows(2).all(|pair| pair[0] < pair[1]) { + return Err(Error::IndicesNotStrictIncreasing); + } + if let Some(last) = indices.last() { + if *last >= self.0.len_in() { + return Err(Error::IndexOutOfRange); + } + } + let mut maps = Vec::new(); + let mut offset = 0; + let mut start = 0; + for map in self.0.iter() { + let stop = start + indices[start..].partition_point(|i| *i < offset + map.len_in()); + if stop > start { + let map_indices: Vec<_> = + indices[start..stop].iter().map(|i| *i - offset).collect(); + start = stop; + let primitive = Primitive::new_take(map_indices, map.len_in()); + maps.push(map.clone_and_push(primitive).unwrap()); + } + offset += map.len_in(); + } + assert_eq!(start, indices.len()); + Ok(Self(UniformConcat::new_unchecked( + maps, + self.dim_in(), + self.delta_dim(), + self.len_out(), + ))) + } + fn clone_and_push(&self, primitive: Primitive) -> Result { + if self.0.dim_in() < primitive.dim_out() { + return Err(Error::DimensionMismatch); + } + if self.0.len_in() % primitive.mod_out() != 0 { + return Err(Error::LengthMismatch); + } + let maps = self + .0 + .iter() + .map(|map| map.clone_and_push(primitive.clone()).unwrap()); + Ok(Self(UniformConcat::new_unchecked( + maps.collect(), + self.dim_in() - primitive.delta_dim(), + self.delta_dim() + primitive.delta_dim(), + self.len_out() / primitive.mod_out() * primitive.mod_in(), + ))) + } + pub fn children(&self, simplex: Simplex, offset: usize) -> Result { + self.clone_and_push(Primitive::new_children(simplex).with_offset(offset)) + } + pub fn edges(&self, simplex: Simplex, offset: usize) -> Result { + self.clone_and_push(Primitive::new_edges(simplex).with_offset(offset)) + } + pub fn uniform_points( + &self, + points: impl Into>, + point_dim: usize, + offset: usize, + ) -> Result { + self.clone_and_push(Primitive::new_uniform_points(points, point_dim).with_offset(offset)) + } + pub fn apply_inplace( + &self, + index: usize, + coords: &mut [f64], + stride: usize, + ) -> Result { + self.0.apply_inplace(index, coords, stride, 0) + } + pub fn apply_index(&self, index: usize) -> Option { + self.0.apply_index(index) + } + pub fn unapply_indices(&self, indices: &[T]) -> Option> { + self.0.unapply_indices(indices) + } +} + +//impl Deref for Transforms { +// type Target = OptionReorder>, Vec>; +// +// fn deref(&self) -> Self::Target { +// self.0 +// } +//} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $(-> $ret:ty)? + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; +} + +impl Map for Transforms { + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Result} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} + dispatch! {fn is_index_map(&self) -> bool} +} + +impl RelativeTo for Transforms { + type Output = + as RelativeTo>>::Output; + + fn relative_to(&self, target: &Self) -> Option { + self.0.relative_to(&target.0) + } +} + +impl Mul for &UniformTransforms { + type Output = UniformTransforms; + + fn mul(self, rhs: Self) -> UniformTransforms { + UniformTransforms::mul(self, rhs) + } +} + +impl Mul for UniformTransforms { + type Output = UniformTransforms; + + fn mul(self, rhs: Self) -> UniformTransforms { + UniformTransforms::mul(&self, &rhs) + } +} + +impl Mul for &Transforms { + type Output = Transforms; + + fn mul(self, rhs: Self) -> Transforms { + Transforms::mul(self, rhs) + } +} + +impl Mul for Transforms { + type Output = Transforms; + + fn mul(self, rhs: Self) -> Transforms { + Transforms::mul(&self, &rhs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::simplex::Simplex::*; + use approx::assert_abs_diff_eq; + + #[test] + fn children() { + let tess = Transforms::identity(vec![Line], 1).children(); + assert_eq!(tess.len(), 2); + let tess = tess.children(); + assert_eq!(tess.len(), 4); + } + + #[test] + fn product() { + let lhs = Transforms::identity(vec![Line], 1).children(); + let rhs = Transforms::identity(vec![Line], 1).edges().unwrap(); + let tess = &lhs * &rhs; + let centroids = tess.centroids(); + let stride = 2; + let mut work: Vec<_> = iter::repeat(-1.0).take(stride).collect(); + println!("tess: {tess:?}"); + assert_eq!(centroids.apply_inplace(0, &mut work, stride), Ok(0)); + assert_abs_diff_eq!(work[..], [0.25, 1.0]); + assert_eq!(centroids.apply_inplace(1, &mut work, stride), Ok(0)); + assert_abs_diff_eq!(work[..], [0.25, 0.0]); + assert_eq!(centroids.apply_inplace(2, &mut work, stride), Ok(0)); + assert_abs_diff_eq!(work[..], [0.75, 1.0]); + assert_eq!(centroids.apply_inplace(3, &mut work, stride), Ok(0)); + assert_abs_diff_eq!(work[..], [0.75, 0.0]); + } + + #[test] + fn take() { + let lhs = Transforms::identity(vec![Line], 1).children(); + let rhs = Transforms::identity(vec![Line], 1).edges().unwrap(); + let levels: Vec<_> = iter::successors(Some(&lhs * &rhs), |level| Some(level.children())) + .take(3) + .collect(); + let hierarch = levels[1].take(&[0, 1, 2]); + } +} From fa21e49051edf4839ca3d7cd7e89b083551dc734 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Thu, 7 Jul 2022 16:20:28 +0200 Subject: [PATCH 40/45] WIP --- src/lib.rs | 32 +++++++++++++++++-- src/map/mod.rs | 2 ++ src/map/ops.rs | 31 ++++++++++++++++++ src/map/primitive.rs | 71 ++++++++++++++++++++++++++++++++++++++++++ src/map/relative.rs | 7 +++++ src/map/tesselation.rs | 2 ++ src/map/transforms.rs | 48 ++-------------------------- src/simplex.rs | 69 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 214 insertions(+), 48 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8f616e82d..5ed11e959 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use map::relative::RelativeTo; use map::tesselation::Tesselation; use map::transforms::Transforms; use map::Map; -use numpy::{IntoPyArray, IxDyn, PyArray, PyArrayDyn, PyReadonlyArrayDyn, PyReadonlyArray2}; +use numpy::{IntoPyArray, IxDyn, PyArray, PyArrayDyn, PyReadonlyArrayDyn, PyReadonlyArray2, PyArray2}; use pyo3::exceptions::{PyIndexError, PyValueError}; use pyo3::prelude::*; use simplex::Simplex; @@ -127,7 +127,7 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { #[pymethods] impl PyTesselation { #[staticmethod] - pub fn identity(shapes: Vec, len: usize) -> Self { + pub fn new_identity(shapes: Vec, len: usize) -> Self { let shapes = shapes.iter().map(|shape| shape.into()).collect(); Tesselation::identity(shapes, len).into() } @@ -258,7 +258,7 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { #[pymethods] impl PyTransforms { #[staticmethod] - pub fn identity(dim: usize, len: usize) -> Self { + pub fn new_identity(dim: usize, len: usize) -> Self { Transforms::identity(dim, len).into() } pub fn __repr__(&self) -> String { @@ -332,6 +332,18 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { .map(|rel| PyRelativeTransforms(rel)) .ok_or(PyValueError::new_err("cannot make relative")) } + pub fn basis<'py>(&self, py: Python<'py>, index: usize) -> PyResult<&'py PyArray2> { + if index >= self.0.len_in() { + return Err(PyIndexError::new_err("index out of range")); + } + let mut basis: Vec = iter::repeat(0.0).take(self.0.dim_out() * self.0.dim_out()).collect(); + for i in 0..self.0.dim_in() { + basis[i * self.0.dim_out() + i] = 1.0; + } + let mut dim_in = self.0.dim_in(); + self.0.update_basis(index, &mut basis[..], self.0.dim_out(), &mut dim_in, 0); + PyArray::from_vec(py, basis).reshape([self.0.dim_out(), self.0.dim_out()]) + } } impl From for PyTransforms { @@ -340,6 +352,8 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { } } + m.add_class::()?; + #[pyclass(name = "RelativeTransforms", module = "nutils._rust")] #[derive(Debug, Clone)] struct PyRelativeTransforms(>::Output); @@ -386,6 +400,18 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { }) .ok_or(PyValueError::new_err("index out of range")) } + pub fn basis<'py>(&self, py: Python<'py>, index: usize) -> PyResult<&'py PyArray2> { + if index >= self.0.len_in() { + return Err(PyIndexError::new_err("index out of range")); + } + let mut basis: Vec = iter::repeat(0.0).take(self.0.dim_out() * self.0.dim_out()).collect(); + for i in 0..self.0.dim_in() { + basis[i * self.0.dim_out() + i] = 1.0; + } + let mut dim_in = self.0.dim_in(); + self.0.update_basis(index, &mut basis[..], self.0.dim_out(), &mut dim_in, 0); + PyArray::from_vec(py, basis).reshape([self.0.dim_out(), self.0.dim_out()]) + } } m.add_class::()?; diff --git a/src/map/mod.rs b/src/map/mod.rs index eb3db31db..f184be19f 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -120,6 +120,8 @@ pub trait Map { fn is_identity(&self) -> bool; /// Returns true if this map returns coordinates unaltered. fn is_index_map(&self) -> bool; + // TODO: return Result, add index, dimension checks + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize; } pub trait AddOffset { diff --git a/src/map/ops.rs b/src/map/ops.rs index c30a55e32..9619fa503 100644 --- a/src/map/ops.rs +++ b/src/map/ops.rs @@ -89,6 +89,11 @@ impl Map for BinaryComposition { fn is_index_map(&self) -> bool { self.0.is_index_map() && self.1.is_index_map() } + #[inline] + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + let index = self.1.update_basis(index, basis, dim_out, dim_in, offset); + self.0.update_basis(index, basis, dim_out, dim_in, offset) + } } /// The composition of an unempty sequence of maps. @@ -203,6 +208,10 @@ where fn is_index_map(&self) -> bool { self.iter().all(|map| map.is_index_map()) } + #[inline] + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + self.iter().rev().fold(index, |index, map| map.update_basis(index, basis, dim_out, dim_in, offset)) + } } /// The concatenation of two maps. @@ -302,6 +311,14 @@ impl Map for BinaryConcat { fn is_index_map(&self) -> bool { self.0.is_index_map() && self.1.is_index_map() } + #[inline] + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + if index < self.0.len_in() { + self.0.update_basis(index, basis, dim_out, dim_in, offset) + } else { + self.1.update_basis(index - self.0.len_in(), basis, dim_out, dim_in, offset) + } + } } /// The concatenation of an unempty sequence of maps. @@ -433,6 +450,16 @@ where fn is_index_map(&self) -> bool { self.iter().all(|item| item.is_index_map()) } + #[inline] + fn update_basis(&self, mut index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + for map in self.iter() { + if index < map.len_in() { + return map.update_basis(index, basis, dim_out, dim_in, offset) + } + index -= map.len_in(); + } + unreachable! {} + } } /// The product of two maps. @@ -528,6 +555,9 @@ impl Map for BinaryProduct { fn is_index_map(&self) -> bool { self.0.is_index_map() && self.1.is_index_map() } + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + unimplemented!{} + } } #[derive(Debug, Clone)] @@ -727,6 +757,7 @@ where dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} + dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} } impl FromIterator for UniformProduct> { diff --git a/src/map/primitive.rs b/src/map/primitive.rs index 2c4d21ad1..1bd628e75 100644 --- a/src/map/primitive.rs +++ b/src/map/primitive.rs @@ -56,6 +56,7 @@ pub trait UnboundedMap { fn is_index_map(&self) -> bool { self.dim_out() == 0 } + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize; } fn coords_iter_mut( @@ -79,18 +80,23 @@ fn coords_iter_mut( pub struct Identity; impl UnboundedMap for Identity { + #[inline] fn dim_in(&self) -> usize { 0 } + #[inline] fn delta_dim(&self) -> usize { 0 } + #[inline] fn mod_in(&self) -> usize { 1 } + #[inline] fn mod_out(&self) -> usize { 1 } + #[inline] fn apply_inplace( &self, index: usize, @@ -100,16 +106,24 @@ impl UnboundedMap for Identity { ) -> usize { index } + #[inline] fn apply_index(&self, index: usize) -> usize { index } + #[inline] fn apply_indices_inplace(&self, _indices: &mut [usize]) {} + #[inline] fn unapply_indices(&self, indices: &[T]) -> Vec { indices.to_vec() } + #[inline] fn is_identity(&self) -> bool { true } + #[inline] + fn update_basis(&self, index: usize, _basis: &mut [f64], _dim_out: usize, _dim_in: &mut usize, _offset: usize) -> usize { + index + } } impl AddOffset for Identity { @@ -166,6 +180,10 @@ impl UnboundedMap for Offset { fn is_identity(&self) -> bool { self.0.is_identity() } + #[inline] + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + self.0.update_basis(index, basis, dim_out, dim_in, offset + self.1) + } } impl AddOffset for Offset { @@ -229,6 +247,10 @@ impl UnboundedMap for Transpose { }) .collect() } + #[inline] + fn update_basis(&self, index: usize, _basis: &mut [f64], _dim_out: usize, _dim_in: &mut usize, _offset: usize) -> usize { + self.apply_index(index) + } } impl AddOffset for Transpose { @@ -297,6 +319,10 @@ impl UnboundedMap for Take { }) .collect() } + #[inline] + fn update_basis(&self, index: usize, _basis: &mut [f64], _dim_out: usize, _dim_in: &mut usize, _offset: usize) -> usize { + self.apply_index(index) + } } impl AddOffset for Take { @@ -358,6 +384,10 @@ impl UnboundedMap for Slice { }) .collect() } + #[inline] + fn update_basis(&self, index: usize, _basis: &mut [f64], _dim_out: usize, _dim_in: &mut usize, _offset: usize) -> usize { + self.apply_index(index) + } } impl AddOffset for Slice { @@ -407,6 +437,9 @@ impl UnboundedMap for Children { .flat_map(|i| (0..self.mod_in()).map(move |j| i.set(i.get() * self.mod_in() + j))) .collect() } + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + self.0.update_child_basis(index, basis, dim_out, dim_in, offset) + } } impl From for Children { @@ -458,6 +491,9 @@ impl UnboundedMap for Edges { .flat_map(|i| (0..self.mod_in()).map(move |j| i.set(i.get() * self.mod_in() + j))) .collect() } + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + self.0.update_edge_basis(index, basis, dim_out, dim_in, offset) + } } impl From for Edges { @@ -526,6 +562,32 @@ impl UnboundedMap for UniformPoints { .flat_map(|i| (0..self.mod_in()).map(move |j| i.set(i.get() * self.mod_in() + j))) .collect() } + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + assert!(offset <= *dim_in); + assert!(*dim_in + self.point_dim < dim_out); + // Shift rows `offset..dim_in` `self.point_dim` rows down. + for i in (offset..*dim_in).into_iter().rev() { + for j in 0..*dim_in { + basis[(i + self.point_dim) * dim_out + j] = basis[i * dim_out + j]; + } + } + for i in (offset..offset + self.point_dim) { + for j in 0..*dim_in { + basis[i * self.point_dim + j] = 0.0; + } + } + // Append identity columns. + for i in 0..*dim_in + self.point_dim { + for j in 0..self.point_dim { + basis[i * dim_out + j] = 0.0; + } + } + for i in 0..self.point_dim { + basis[(i + offset) * dim_out + *dim_in + i] = 1.0; + } + *dim_in += self.point_dim; + index / self.npoints + } } /// An enum of primitive maps. @@ -637,6 +699,7 @@ impl UnboundedMap for Primitive { dispatch! {fn unapply_indices(&self, indices: &[T]) -> Vec} dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} + dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} } impl AddOffset for Primitive { @@ -759,6 +822,10 @@ where fn is_identity(&self) -> bool { self.iter().all(|map| map.is_identity()) } + #[inline] + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + self.iter().rev().fold(index, |index, map| map.update_basis(index, basis, dim_out, dim_in, offset)) + } } impl AddOffset for Array @@ -864,6 +931,10 @@ impl Map for WithBounds { fn is_index_map(&self) -> bool { self.map.is_index_map() } + #[inline] + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + self.map.update_basis(index, basis, dim_out, dim_in, offset) + } } impl AddOffset for WithBounds diff --git a/src/map/relative.rs b/src/map/relative.rs index b598118e4..9ea1491cf 100644 --- a/src/map/relative.rs +++ b/src/map/relative.rs @@ -251,6 +251,7 @@ impl Map for Relative { dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} + dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} } //impl AddOffset for Relative { @@ -396,6 +397,12 @@ impl Map for RelativeToConcat { fn is_index_map(&self) -> bool { false // TODO } + fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { + let (iout, iin) = self.index_map[index]; + let n = self.index_map.len(); + self.rels[iin / n].update_basis(iin % n, basis, dim_out, dim_in, offset); + iout + } } //impl AddOffset for RelativeToConcat { diff --git a/src/map/tesselation.rs b/src/map/tesselation.rs index a6b866a5b..c7803ce10 100644 --- a/src/map/tesselation.rs +++ b/src/map/tesselation.rs @@ -156,6 +156,7 @@ impl Map for UniformTesselation { dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} + dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} } impl AllPrimitiveDecompositions for UniformTesselation { @@ -344,6 +345,7 @@ impl Map for Tesselation { dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} + dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} } impl RelativeTo for Tesselation { diff --git a/src/map/transforms.rs b/src/map/transforms.rs index 429cc6c1b..1b74fc61b 100644 --- a/src/map/transforms.rs +++ b/src/map/transforms.rs @@ -100,6 +100,7 @@ impl Map for UniformTransforms { dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} + dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} } impl AllPrimitiveDecompositions for UniformTransforms { @@ -196,7 +197,7 @@ impl Transforms { maps.collect(), self.dim_in() - primitive.delta_dim(), self.delta_dim() + primitive.delta_dim(), - self.len_out() / primitive.mod_out() * primitive.mod_in(), + self.len_out(), ))) } pub fn children(&self, simplex: Simplex, offset: usize) -> Result { @@ -272,6 +273,7 @@ impl Map for Transforms { dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} + dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} } impl RelativeTo for Transforms { @@ -314,47 +316,3 @@ impl Mul for Transforms { Transforms::mul(&self, &rhs) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::simplex::Simplex::*; - use approx::assert_abs_diff_eq; - - #[test] - fn children() { - let tess = Transforms::identity(vec![Line], 1).children(); - assert_eq!(tess.len(), 2); - let tess = tess.children(); - assert_eq!(tess.len(), 4); - } - - #[test] - fn product() { - let lhs = Transforms::identity(vec![Line], 1).children(); - let rhs = Transforms::identity(vec![Line], 1).edges().unwrap(); - let tess = &lhs * &rhs; - let centroids = tess.centroids(); - let stride = 2; - let mut work: Vec<_> = iter::repeat(-1.0).take(stride).collect(); - println!("tess: {tess:?}"); - assert_eq!(centroids.apply_inplace(0, &mut work, stride), Ok(0)); - assert_abs_diff_eq!(work[..], [0.25, 1.0]); - assert_eq!(centroids.apply_inplace(1, &mut work, stride), Ok(0)); - assert_abs_diff_eq!(work[..], [0.25, 0.0]); - assert_eq!(centroids.apply_inplace(2, &mut work, stride), Ok(0)); - assert_abs_diff_eq!(work[..], [0.75, 1.0]); - assert_eq!(centroids.apply_inplace(3, &mut work, stride), Ok(0)); - assert_abs_diff_eq!(work[..], [0.75, 0.0]); - } - - #[test] - fn take() { - let lhs = Transforms::identity(vec![Line], 1).children(); - let rhs = Transforms::identity(vec![Line], 1).edges().unwrap(); - let levels: Vec<_> = iter::successors(Some(&lhs * &rhs), |level| Some(level.children())) - .take(3) - .collect(); - let hierarch = levels[1].take(&[0, 1, 2]); - } -} diff --git a/src/simplex.rs b/src/simplex.rs index 3a5e7748b..08d946612 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -228,6 +228,57 @@ impl Simplex { let n = self.nedges(); indices.flat_map(move |i| (0..n).map(move |j| i * n + j)) } + pub fn update_child_basis( + &self, + index: usize, + basis: &mut [f64], + dim_out: usize, + dim_in: &mut usize, + offset: usize, + ) -> usize { + assert!(offset + self.dim() <= *dim_in); + match self { + Self::Line => { + for i in 0..*dim_in { + basis[i * dim_out + offset] *= 0.5; + } + index / 2 + } + Self::Triangle => unimplemented! {}, + } + } + pub fn update_edge_basis( + &self, + index: usize, + basis: &mut [f64], + dim_out: usize, + dim_in: &mut usize, + offset: usize, + ) -> usize { + assert!(offset + self.edge_dim() <= *dim_in); + // Shift rows `offset..dim_in` one row down. + for i in (offset..*dim_in).into_iter().rev() { + for j in 0..*dim_in { + basis[(i + 1) * dim_out + j] = basis[i * dim_out + j]; + } + } + for j in 0..*dim_in { + basis[offset * dim_out + j] = 0.0; + } + // Zero the normal. + for i in 0..*dim_in + 1 { + basis[i * dim_out + *dim_in] = 0.0; + } + let index = match self { + Self::Line => { + basis[offset * dim_out + *dim_in] = if index % 2 == 0 { 1.0 } else { -1.0 }; + index / 2 + } + Self::Triangle => unimplemented! {}, + }; + *dim_in += 1; + index + } } #[cfg(test)] @@ -372,4 +423,22 @@ mod tests { } } } + + #[test] + fn child_basis() { + let mut basis = vec![1.0, 0.0, 0.0, 1.0]; + let mut dim_in = 2; + assert_eq!(Line.update_child_basis(0, &mut basis, 2, &mut dim_in, 0), 0); + assert_eq!(dim_in, 2); + assert_abs_diff_eq!(basis[..], [0.5, 0.0, 0.0, 1.0]); + } + + #[test] + fn edge_basis() { + let mut basis = vec![1.0, 0.0, 0.0, 0.0]; + let mut dim_in = 1; + assert_eq!(Line.update_edge_basis(1, &mut basis, 2, &mut dim_in, 1), 0); + assert_eq!(dim_in, 2); + assert_abs_diff_eq!(basis[..], [1.0, 0.0, 0.0, -1.0]); + } } From 2f4ca486f56c98ba7c8628bf7a73c048f89f0aab Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Thu, 7 Jul 2022 20:48:43 +0200 Subject: [PATCH 41/45] WIP --- nutils/evaluable.py | 93 ++++++--------------------------------------- nutils/function.py | 18 +++++---- nutils/sample.py | 5 ++- src/lib.rs | 38 +++++++++++++----- 4 files changed, 53 insertions(+), 101 deletions(-) diff --git a/nutils/evaluable.py b/nutils/evaluable.py index 79d208eb6..10906f4de 100644 --- a/nutils/evaluable.py +++ b/nutils/evaluable.py @@ -3878,7 +3878,7 @@ def _intbounds_impl(self): return 0, upper_length - 1 -class TransformsRootCoords(Array): +class TransformsApplyCoords(Array): def __init__(self, transforms, index: Array, coords: Array): if index.dtype != int or index.ndim != 0: @@ -3891,102 +3891,39 @@ def __init__(self, transforms, index: Array, coords: Array): super().__init__(args=[index, coords], shape=(*coords.shape[:-1], transforms.todims), dtype=float) def evalf(self, index, coords): - chain = self._transforms[index.__index__()] - return functools.reduce(lambda c, t: t.apply(c), reversed(chain), coords) + return self._transforms.apply(index.__index__(), coords)[1] def _derivative(self, var, seen): - linear = TransformsRootLinear(self._transforms, self._index) - dcoords = derivative(self._coords, var, seen) - return einsum('ij,AjB->AiB', linear, dcoords, A=self._coords.ndim - 1, B=var.ndim) - - -class TransformsRelativeCoords(Array): - - def __init__(self, target, transforms, index: Array, coords: Array): - if index.dtype != int or index.ndim != 0: - raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') - if coords.dtype != float: - raise ValueError('argument `coords` must be a real-valued array with at least one axis') - self._target = target - self._transforms = transforms - self._index = index - self._coords = coords - super().__init__(args=[index, coords], shape=(*coords.shape[:-1], target.fromdims), dtype=float) - - def evalf(self, index, coords): - _, chain = self._target.index_with_tail(self._transforms[index.__index__()]) - return functools.reduce(lambda c, t: t.apply(c), reversed(chain), coords) - - def _derivative(self, var, seen): - linear = TransformsRelativeLinear(self._target, self._transforms, self._index) + linear = TransformsBasis(self._transforms, self._index)[:,:self._transforms.fromdims] dcoords = derivative(self._coords, var, seen) return einsum('ij,AjB->AiB', linear, dcoords, A=self._coords.ndim - 1, B=var.ndim) def _simplified(self): - if self._target == self._transforms: + if self._transforms.is_index_map: return self._coords -class TransformsRelativeIndex(Array): +class TransformsApplyIndex(Array): - def __init__(self, target, transforms, index: Array): + def __init__(self, transforms, index: Array): if index.dtype != int or index.ndim != 0: raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') - self._target = target self._transforms = transforms self._index = index super().__init__(args=[index], shape=(), dtype=int) def evalf(self, index): - index, _ = self._target.index_with_tail(self._transforms[index.__index__()]) - return numpy.array(index) + return numpy.array(self._transforms.apply_index(index.__index__())) def _intbounds_impl(self): - return 0, len(self._target) - 1 + return 0, len(self._transforms) - 1 def _simplified(self): - if self._target == self._transforms: + if self._transforms.is_identity: return self._index -class TransformsRootLinear(Array): - - def __init__(self, transforms, index: Array): - if index.dtype != int or index.ndim != 0: - raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') - self._transforms = transforms - super().__init__(args=[index], shape=(transforms.todims, transforms.fromdims), dtype=float) - - def evalf(self, index): - chain = self._transforms[index.__index__()] - if chain: - return functools.reduce(lambda r, i: i @ r, (item.linear for item in reversed(chain))) - else: - return numpy.eye(self._transforms.fromdims) - - -class TransformsRelativeLinear(Array): - - def __init__(self, target, transforms, index: Array): - if index.dtype != int or index.ndim != 0: - raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') - self._target = target - self._transforms = transforms - super().__init__(args=[index], shape=(target.fromdims, transforms.fromdims), dtype=float) - - def evalf(self, index): - _, chain = self._target.index_with_tail(self._transforms[index.__index__()]) - if chain: - return functools.reduce(lambda r, i: i @ r, (item.linear for item in reversed(chain))) - else: - return numpy.eye(self._transforms.fromdims) - - def _simplified(self): - if self._target == self._transforms: - return diagonalize(ones((self._transforms.fromdims,))) - - -class TransformsRootBasis(Array): +class TransformsBasis(Array): def __init__(self, transforms, index: Array): if index.dtype != int or index.ndim != 0: @@ -3995,15 +3932,7 @@ def __init__(self, transforms, index: Array): super().__init__(args=[index], shape=(transforms.todims, transforms.todims), dtype=float) def evalf(self, index): - chain = self._transforms[index.__index__()] - linear = numpy.eye(self._transforms.fromdims) - for item in reversed(chain): - linear = item.linear @ linear - assert item.fromdims <= item.todims <= item.fromdims + 1 - if item.todims == item.fromdims + 1: - linear = numpy.concatenate([linear, item.ext[:, numpy.newaxis]], axis=1) - assert linear.shape == (self._transforms.todims, self._transforms.todims) - return linear + return self._transforms.basis(index.__index__()) class _LoopIndex(Argument): diff --git a/nutils/function.py b/nutils/function.py index 5a7e0e9fe..6bad2c978 100644 --- a/nutils/function.py +++ b/nutils/function.py @@ -6,7 +6,7 @@ from typing import Tuple, Union, Type, Callable, Sequence, Any, Optional, Iterator, Iterable, Dict, Mapping, List, FrozenSet, NamedTuple from . import evaluable, numeric, util, types, warnings, debug_flags, sparse -from .transformseq import Transforms +from ._rust import Transforms import builtins import numpy import functools @@ -26,7 +26,7 @@ class LowerArgs(NamedTuple): points_shape : :class:`tuple` of scalar, integer :class:`nutils.evaluable.Array` The shape of the leading points axes that are to be added to the lowered :class:`nutils.evaluable.Array`. - transform_chains : mapping of :class:`str` to tuples of :class:`nutils.transformseq.Transforms` and :class:`nutils.evaluable.Array` + transform_chains : mapping of :class:`str` to tuples of :class:`nutils._rust.Transforms` and :class:`nutils.evaluable.Array` A mapping of spaces to transforms sequences and evaluable indices. coordinates : mapping of :class:`str` to :class:`nutils.evaluable.Array` objects The coordinates at which the function will be evaluated. @@ -963,7 +963,7 @@ def lower(self, args: LowerArgs) -> evaluable.Array: tip_coords = args.coordinates[self._space] tip_coords = evaluable.WithDerivative(tip_coords, _tip_derivative_target(self._space, tip_coords.shape[-1]), evaluable.Diagonalize(evaluable.ones(tip_coords.shape))) transforms, index = args.transform_chains[self._space] - coords = evaluable.TransformsRootCoords(transforms[0], index, tip_coords) + coords = evaluable.TransformsApplyCoords(transforms[0], index, tip_coords) return evaluable.WithDerivative(coords, _root_derivative_target(self._space, self.shape[0]), inv_linear) @@ -976,7 +976,8 @@ def __init__(self, space: str, transforms: Transforms) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: transforms, index = args.transform_chains[self._space] - return evaluable.prependaxes(evaluable.TransformsRelativeIndex(self._transforms, transforms[0], index), args.points_shape) + relative = transforms[0].relative_to(self._transforms) + return evaluable.prependaxes(evaluable.TransformsApplyIndex(relative, index), args.points_shape) class _TransformsCoords(Array): @@ -988,8 +989,9 @@ def __init__(self, space: str, transforms: Transforms) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: transforms, tip_index = args.transform_chains[self._space] - index = evaluable.TransformsRelativeIndex(self._transforms, transforms[0], tip_index) - L = evaluable.TransformsRootLinear(self._transforms, index) + relative = transforms[0].relative_to(self._transforms) + index = evaluable.TransformsApplyIndex(relative, tip_index) + L = evaluable.TransformsBasis(self._transforms, index)[:,:self._transforms.fromdims] if self._transforms.todims > self._transforms.fromdims: LTL = evaluable.einsum('ki,kj->ij', L, L) Linv = evaluable.einsum('ik,jk->ij', evaluable.inverse(LTL), L) @@ -998,7 +1000,7 @@ def lower(self, args: LowerArgs) -> evaluable.Array: Linv = evaluable.prependaxes(Linv, args.points_shape) tip_coords = args.coordinates[self._space] tip_coords = evaluable.WithDerivative(tip_coords, _tip_derivative_target(self._space, tip_coords.shape[-1]), evaluable.Diagonalize(evaluable.ones(tip_coords.shape))) - coords = evaluable.TransformsRelativeCoords(self._transforms, transforms[0], tip_index, tip_coords) + coords = evaluable.TransformsApplyCoords(relative, tip_index, tip_coords) return evaluable.WithDerivative(coords, _root_derivative_target(self._space, self._transforms.todims), Linv) @@ -1134,7 +1136,7 @@ def lower(self, args: LowerArgs) -> evaluable.Array: tangents.append(rgrad) else: assert normal is None and chain.todims == chain.fromdims + 1 - basis = evaluable.einsum('Aij,jk->Aik', rgrad, evaluable.TransformsRootBasis(chain, index)) + basis = evaluable.einsum('Aij,jk->Aik', rgrad, evaluable.TransformsBasis(chain, index)) tangents.append(basis[..., :chain.fromdims]) normal = basis[..., chain.fromdims:] assert normal is not None diff --git a/nutils/sample.py b/nutils/sample.py index 85cf575b0..d20c8ae90 100644 --- a/nutils/sample.py +++ b/nutils/sample.py @@ -925,8 +925,9 @@ def lower(self, args: function.LowerArgs) -> evaluable.Array: where = tuple(sorted(where)) (chain, *_), tip_index = args.transform_chains[self._sample.space] - index = evaluable.TransformsRelativeIndex(self._sample.transforms[0], chain, tip_index) - coords = evaluable.TransformsRelativeCoords(self._sample.transforms[0], chain, tip_index, space_coords) + relative = chain.relative_to(self._sample.transforms[0]) + index = evaluable.TransformsApplyIndex(relative, tip_index) + coords = evaluable.TransformsApplyCoords(relative, tip_index, space_coords) expect = self._sample.points.get_evaluable_coords(index) sampled = evaluable.Sampled(coords, expect) indices = self._sample.get_evaluable_indices(index) diff --git a/src/lib.rs b/src/lib.rs index 5ed11e959..de2e8471b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -344,6 +344,14 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { self.0.update_basis(index, &mut basis[..], self.0.dim_out(), &mut dim_in, 0); PyArray::from_vec(py, basis).reshape([self.0.dim_out(), self.0.dim_out()]) } + #[getter] + pub fn is_identity(&self) -> bool { + self.0.is_identity() + } + #[getter] + pub fn is_index_map(&self) -> bool { + self.0.is_index_map() + } } impl From for PyTransforms { @@ -363,20 +371,24 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { pub fn __repr__(&self) -> String { format!("{:?}", self.0) } - pub fn len_out(&self) -> usize { - self.0.len_out() - } - pub fn len_in(&self) -> usize { + pub fn __len__(&self) -> usize { self.0.len_in() } - pub fn dim_out(&self) -> usize { - self.0.dim_out() + #[getter] + pub fn fromlen(&self) -> usize { + self.0.len_out() } - pub fn dim_in(&self) -> usize { + #[getter] + pub fn tolen(&self) -> usize { + self.0.len_out() + } + #[getter] + pub fn fromdims(&self) -> usize { self.0.dim_in() } - pub fn delta_dim(&self) -> usize { - self.0.delta_dim() + #[getter] + pub fn todims(&self) -> usize { + self.0.dim_out() } pub fn apply_index(&self, index: usize) -> PyResult { self.0 @@ -412,6 +424,14 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { self.0.update_basis(index, &mut basis[..], self.0.dim_out(), &mut dim_in, 0); PyArray::from_vec(py, basis).reshape([self.0.dim_out(), self.0.dim_out()]) } + #[getter] + pub fn is_identity(&self) -> bool { + self.0.is_identity() + } + #[getter] + pub fn is_index_map(&self) -> bool { + self.0.is_index_map() + } } m.add_class::()?; From 1c3d40865f4bc482943071a5a608f4c02bc6e0a5 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Fri, 22 Jul 2022 00:31:14 +0200 Subject: [PATCH 42/45] WIP --- nutils/evaluable.py | 36 ++--- nutils/function.py | 348 +++++++++++++++++++++++++++------------- nutils/sample.py | 84 ++++------ src/lib.rs | 231 +++----------------------- src/map/coord_system.rs | 310 +++++++++++++++++++++++++++++++++++ src/map/mod.rs | 3 +- src/map/primitive.rs | 4 +- 7 files changed, 628 insertions(+), 388 deletions(-) create mode 100644 src/map/coord_system.rs diff --git a/nutils/evaluable.py b/nutils/evaluable.py index 10906f4de..c14bf05d0 100644 --- a/nutils/evaluable.py +++ b/nutils/evaluable.py @@ -3878,61 +3878,61 @@ def _intbounds_impl(self): return 0, upper_length - 1 -class TransformsApplyCoords(Array): +class TransformCoords(Array): - def __init__(self, transforms, index: Array, coords: Array): + def __init__(self, trans, index: Array, coords: Array): if index.dtype != int or index.ndim != 0: raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') if coords.dtype != float: raise ValueError('argument `coords` must be a real-valued array with at least one axis') - self._transforms = transforms + self._trans = trans self._index = index self._coords = coords - super().__init__(args=[index, coords], shape=(*coords.shape[:-1], transforms.todims), dtype=float) + super().__init__(args=[index, coords], shape=(*coords.shape[:-1], trans.to_dim), dtype=float) def evalf(self, index, coords): - return self._transforms.apply(index.__index__(), coords)[1] + return self._trans.apply(index.__index__(), coords)[1] def _derivative(self, var, seen): - linear = TransformsBasis(self._transforms, self._index)[:,:self._transforms.fromdims] + linear = TransformBasis(self._trans, self._index)[:,:self._trans.from_dim] dcoords = derivative(self._coords, var, seen) return einsum('ij,AjB->AiB', linear, dcoords, A=self._coords.ndim - 1, B=var.ndim) def _simplified(self): - if self._transforms.is_index_map: + if self._trans.is_index_map: return self._coords -class TransformsApplyIndex(Array): +class TransformIndex(Array): - def __init__(self, transforms, index: Array): + def __init__(self, trans, index: Array): if index.dtype != int or index.ndim != 0: raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') - self._transforms = transforms + self._trans = trans self._index = index super().__init__(args=[index], shape=(), dtype=int) def evalf(self, index): - return numpy.array(self._transforms.apply_index(index.__index__())) + return numpy.array(self._trans.apply_index(index.__index__())) def _intbounds_impl(self): - return 0, len(self._transforms) - 1 + return 0, len(self._trans) - 1 def _simplified(self): - if self._transforms.is_identity: + if self._trans.is_identity: return self._index -class TransformsBasis(Array): +class TransformBasis(Array): - def __init__(self, transforms, index: Array): + def __init__(self, trans, index: Array): if index.dtype != int or index.ndim != 0: raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') - self._transforms = transforms - super().__init__(args=[index], shape=(transforms.todims, transforms.todims), dtype=float) + self._trans = trans + super().__init__(args=[index], shape=(trans.to_dim, trans.to_dim), dtype=float) def evalf(self, index): - return self._transforms.basis(index.__index__()) + return self._trans.basis(index.__index__()) class _LoopIndex(Argument): diff --git a/nutils/function.py b/nutils/function.py index 6bad2c978..d6bcd6f4a 100644 --- a/nutils/function.py +++ b/nutils/function.py @@ -6,7 +6,7 @@ from typing import Tuple, Union, Type, Callable, Sequence, Any, Optional, Iterator, Iterable, Dict, Mapping, List, FrozenSet, NamedTuple from . import evaluable, numeric, util, types, warnings, debug_flags, sparse -from ._rust import Transforms +from ._rust import CoordSystem import builtins import numpy import functools @@ -18,6 +18,66 @@ DType = Type[Union[bool, int, float, complex]] _dtypes = bool, int, float, complex +class Bound: + + def __init__(self, reference: Dict[str, CoordSystem], coord_systems: Tuple[CoordSystem, ...], index: evaluable.Array, coords: Optional[evaluable.Array]): + if not isinstance(reference, dict) or not all(isinstance(key, str) and isinstance(val, CoordSystem) for key, val in reference.items()): + raise ValueError('argument `reference`: expected a `dict` or `str` to `CoordSystem`s') + elif not reference: + raise ValueError('argument `reference` must not be empty') + + if not isinstance(coord_systems, tuple) or not all(isinstance(c, CoordSystem) for c in coord_systems): + raise ValueError('argument `coord_systems`: expected a `tuple` of `CoordSystem`s') + if len(coord_systems) == 0: + raise ValueError('argument `coord_systems` must not be empty') + n = len(coord_systems[0]) + dim = coord_systems[0].dim + if not all(len(c) == n and c.dim == dim for c in coord_systems[1:]): + raise ValueError('argument `coord_systems`: the dimensions vary') + + if index.dtype != int or index.ndim != 0: + raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + + self.reference = reference + self.spaces = tuple(reference) + self.coord_systems = coord_systems + self.index = index + + if coords is not None: + if not isinstance(coords, evaluable.Array): + raise ValueError('argument `coords` must be an `nutils.evaluable.Array`') + if coords.ndim == 0: + raise ValueError('argument `coords` must have at least one axis') + if not evaluable.equalindex(coords.shape[-1], dim): + raise ValueError('argument `coords`: the last axis does not match the dimension of the coordinate system') + deriv = evaluable.Diagonalize(evaluable.ones(coords.shape)) + coords = evaluable.WithDerivative(coords, self._tip_derivative_target, deriv) + self.coords = coords + + def without_points(self) -> 'Bound': + return Bound(self.reference, self.coord_systems, self.index, None) + + def opposite(self) -> 'Bound': + if len(self.coord_systems) > 1: + return Bound(self.reference, self.coord_systems[::-1], self.index, self.coords) + else: + return self + + def into_lower_args(self) -> 'LowerArgs': + if self.coords is not None: + points_shape = self.coords.shape[:-1] + else: + points_shape = () + return LowerArgs(points_shape, (self,)) + + @property + def dim(self): + return self.coord_systems[0].dim + + @property + def _tip_derivative_target(self): + return evaluable.IdentifierDerivativeTarget(('tip', self.spaces), (self.dim,)) + class LowerArgs(NamedTuple): '''Arguments for Lowerable.lower @@ -26,38 +86,129 @@ class LowerArgs(NamedTuple): points_shape : :class:`tuple` of scalar, integer :class:`nutils.evaluable.Array` The shape of the leading points axes that are to be added to the lowered :class:`nutils.evaluable.Array`. - transform_chains : mapping of :class:`str` to tuples of :class:`nutils._rust.Transforms` and :class:`nutils.evaluable.Array` - A mapping of spaces to transforms sequences and evaluable indices. - coordinates : mapping of :class:`str` to :class:`nutils.evaluable.Array` objects - The coordinates at which the function will be evaluated. + bounds : :class:`tuple` of :class:`Bound` s + A tuple of bounds of one or more spaces to a coordinate systems and + optionally an opposite coordinate system. ''' points_shape: Tuple[evaluable.Array, ...] - transform_chains: Mapping[str, Tuple[Transforms, evaluable.Array]] - coordinates: Mapping[str, evaluable.Array] + bounds: Tuple[Bound, ...] def consistent(self): return all( - evaluable.equalshape(coords.shape[:-1], self.points_shape) - and space in self.transform_chains - for space, coords in self.coordinates.items()) - - @classmethod - def for_space(cls, space: str, transforms: Tuple[Transforms, ...], index: evaluable.Array, coordinates: evaluable.Array) -> 'LowerArgs': - if index.dtype != int or index.ndim != 0: - raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') - return cls(coordinates.shape[:-1], {space: (transforms, index)}, {space: coordinates}) + evaluable.equalshape(bound.coords.shape[:-1], self.points_shape) + for bound in self.bounds + if bound.coords is not None) def __or__(self, other: 'LowerArgs') -> 'LowerArgs': - duplicates = set(self.transform_chains) & set(other.transform_chains) + duplicates = frozenset(self.spaces) & frozenset(other.spaces) if duplicates: raise ValueError(f'Nested integrals or samples in the same space: {", ".join(sorted(duplicates))}.') - transform_chains = self.transform_chains.copy() - transform_chains.update(other.transform_chains) - coordinates = {space: evaluable.Transpose.to_end(evaluable.appendaxes(coords, other.points_shape), coords.ndim - 1) for space, coords in self.coordinates.items()} - coordinates.update({space: evaluable.prependaxes(coords, self.points_shape) for space, coords in other.coordinates.items()}) + bounds = [] + for bound in self.bounds: + coords = evaluable.Transpose.to_end(evaluable.appendaxes(bound.coords, other.points_shape), bound.coords.ndim - 1) + bounds.append(Bound(bound.reference, bound.coord_systems, bound.index, coords)) + for bound in other.bounds: + coords = evaluable.prependaxes(bound.coords, self.points_shape) + bounds.append(Bound(bound.reference, bound.coord_systems, bound.index, coords)) points_shape = self.points_shape + other.points_shape - return LowerArgs(points_shape, transform_chains, coordinates) + return LowerArgs(points_shape, bounds) + + @property + def reference(self) -> Dict[str, CoordSystem]: + reference = {} + for bound in self.bounds: + reference.update(bound.reference) + return reference + + @property + def spaces(self) -> FrozenSet[str]: + return frozenset(space for bound in self.bounds for space in bound.spaces) + + def without_points(self) -> 'LowerArgs': + return LowerArgs((), tuple(bound.without_points() for bound in self.bounds)) + + def opposite(self) -> 'LowerArgs': + return LowerArgs(self.points_shape, tuple(bound.opposite() for bound in self.bounds)) + + def index_coords_at(self, spaces: Tuple[str, ...], coord_system: CoordSystem) -> Tuple[evaluable.Array, Optional[evaluable.Array]]: + if not isinstance(spaces, tuple) or not all(isinstance(space, str) for space in spaces): + raise ValueError('arguent `spaces`: expected a `tuple` of `str`') + missing = frozenset(spaces) - self.spaces + if missing: + raise ValueError('missing spaces: {}'.format(', '.join(sorted(missing)))) + if not isinstance(coord_system, CoordSystem): + raise ValueError('argument `coord_system`: expected an `CoordSystem`') + + orig_len = len(coord_system) + orig_dim = coord_system.dim + + # filter bounds that have at least one space of interest and order to match `spaces` as good as possible + bounds = tuple(bound for bound in self.bounds if not set(bound.spaces).isdisjoint(spaces)) + bounds = tuple(sorted(bounds, key=lambda bound: builtins.min(spaces.index(space) for space in bound.spaces if space in spaces))) + + # extend `coord_system` + bound_reference = {space: ref for bound in bounds for space, ref in bound.reference.items()} + bound_spaces = tuple(bound_reference) + for i in range(len(bound_spaces) - len(spaces) + 1): + if bound_spaces[i:i + len(spaces)] == spaces: + break + else: + raise ValueError('Cannot create a transformation for a non-contiguous subset of bound spaces.') + stride_before = 1 + dim_before = 0 + for space in reversed(bound_spaces[:i]): + ref = bound_reference[space] + coord_system = ref * coord_system + stride_before *= len(ref) + dim_before += ref.dim + stride_after = 1 + dim_after = 0 + for space in bound_spaces[i + len(spaces):]: + ref = bound_reference[space] + coord_system *= ref + stride_after *= len(ref) + dim_after += ref.dim + assert orig_len * stride_before * stride_after == len(coord_system) + assert orig_dim + dim_before + dim_after == coord_system.dim + + bound_coord_system = util.product(bound.coord_systems[0] for bound in bounds) + trans = bound_coord_system.trans_to(coord_system) + + bound_index = bounds[0].index + for bound in bounds[1:]: + bound_index = bound_index * len(bound.coord_system) + bound.index + index = evaluable.TransformIndex(trans, bound_index) + if stride_after != 1: + index = evaluable.FloorDivide(index, stride_after) + if stride_before != 1: + index = evaluable.Mod(index, orig_len) + + if any(bound.coords is None for bound in bounds): + return index, None + + bound_coords = evaluable.concatenate([bound.coords for bound in bounds], axis=-1) + coords = evaluable.TransformCoords(trans, bound_index, bound_coords) + coords = coords[..., dim_before:dim_before + orig_dim] + + bound_reference = util.product(coord_system for bound in bounds for coord_system in bound.reference.values()) + ref_trans = coord_system.trans_to(bound_reference) + L = evaluable.TransformBasis(ref_trans, index)[:,:coord_system.dim] + if bound_reference.dim == coord_system.dim: + Linv = evaluable.inverse(L) + else: + LTL = evaluable.einsum('ki,kj->ij', L, L) + Linv = evaluable.einsum('ik,jk->ij', evaluable.inverse(LTL), L) + Linv = evaluable.prependaxes(Linv, self.points_shape) + Linv = Linv[..., dim_before:dim_before + orig_dim,:] + offset = 0 + for bound in bounds: + for space, reference in bound.reference.items(): + target = _root_derivative_target(space, reference.dim) + coords = evaluable.WithDerivative(coords, target, Linv[...,offset:offset+reference.dim]) + offset += reference.dim + + return index, coords class Lowerable(Protocol): @@ -106,7 +257,7 @@ def _cache_lower(self, args: LowerArgs) -> evaluable.Array: cached_args, cached_result = getattr(self, '_ArrayMeta__cached_lower', (None, None)) if cached_args == args: return cached_result - missing_spaces = self.spaces - set(args.transform_chains) + missing_spaces = self.spaces - set(space for bound in args.bounds for space in bound.spaces) if missing_spaces: raise ValueError('Cannot lower {} because the following spaces are unspecified: {}.'.format(self, missing_spaces)) result = self._ArrayMeta__cache_lower_orig(args) @@ -216,7 +367,7 @@ def lower(self, args: LowerArgs) -> evaluable.Array: @util.cached_property def as_evaluable_array(self) -> evaluable.Array: - return self.lower(LowerArgs((), {}, {})) + return self.lower(LowerArgs((), ())) def __index__(self): if self.arguments or self.spaces: @@ -679,6 +830,7 @@ def lower(self, args: LowerArgs) -> evaluable.Array: evalargs = tuple(arg.lower(args) if isinstance(arg, Array) else evaluable.EvaluableConstant(arg) for arg in self._args) # type: Tuple[Union[evaluable.Array, evaluable.EvaluableConstant], ...] add_points_shape = tuple(map(evaluable.asarray, self.shape[:self._npointwise])) points_shape = args.points_shape + add_points_shape + # FIXME coordinates = {space: evaluable.Transpose.to_end(evaluable.appendaxes(coords, add_points_shape), coords.ndim-1) for space, coords in args.coordinates.items()} return _CustomEvaluable(type(self).__name__, self.evalf, self.partial_derivative, evalargs, self.shape[self._npointwise:], self.dtype, self.spaces, types.frozendict(self.arguments), LowerArgs(points_shape, types.frozendict(args.transform_chains), types.frozendict(coordinates))) @@ -807,7 +959,7 @@ def __init__(self, __arg: Array) -> None: self.arguments = __arg.arguments def lower(self, args: LowerArgs) -> evaluable.Array: - return self._arg.lower(LowerArgs((), args.transform_chains, {})) + return self._arg.lower(args.without_points()) class _Wrapper(Array): @@ -900,7 +1052,7 @@ def __init__(self, arg: Array, replacements: Dict[str, Array]) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: arg = self._arg.lower(args) - replacements = {name: _WithoutPoints(value).lower(args) for name, value in self._replacements.items()} + replacements = {name: value.lower(args.without_points()) for name, value in self._replacements.items()} return evaluable.replace_arguments(arg, replacements) @@ -945,63 +1097,32 @@ def __init__(self, arg: Array, space: str) -> None: super().__init__(arg.shape, arg.dtype, arg.spaces, arg.arguments) def lower(self, args: LowerArgs) -> evaluable.Array: - oppargs = LowerArgs(args.points_shape, dict(args.transform_chains), args.coordinates) - transforms, index = args.transform_chains[self._space] - oppargs.transform_chains[self._space] = transforms[::-1], index - return self._arg.lower(oppargs) - - -class _RootCoords(Array): - - def __init__(self, space: str, ndims: int) -> None: - self._space = space - super().__init__((ndims,), float, frozenset({space}), {}) - - def lower(self, args: LowerArgs) -> evaluable.Array: - inv_linear = evaluable.diagonalize(evaluable.ones(self.shape)) - inv_linear = evaluable.prependaxes(inv_linear, args.points_shape) - tip_coords = args.coordinates[self._space] - tip_coords = evaluable.WithDerivative(tip_coords, _tip_derivative_target(self._space, tip_coords.shape[-1]), evaluable.Diagonalize(evaluable.ones(tip_coords.shape))) - transforms, index = args.transform_chains[self._space] - coords = evaluable.TransformsApplyCoords(transforms[0], index, tip_coords) - return evaluable.WithDerivative(coords, _root_derivative_target(self._space, self.shape[0]), inv_linear) + return self._arg.lower(args.opposite()) -class _TransformsIndex(Array): +class _IndexAtCoordSystem(Array): - def __init__(self, space: str, transforms: Transforms) -> None: - self._space = space - self._transforms = transforms - super().__init__((), int, frozenset({space}), {}) + def __init__(self, spaces: Tuple[str, ...], coord_system: CoordSystem) -> None: + self._spaces = spaces + self._coord_system = coord_system + super().__init__((), int, frozenset(spaces), {}) def lower(self, args: LowerArgs) -> evaluable.Array: - transforms, index = args.transform_chains[self._space] - relative = transforms[0].relative_to(self._transforms) - return evaluable.prependaxes(evaluable.TransformsApplyIndex(relative, index), args.points_shape) + index, coords = args.index_coords_at(self._spaces, self._coord_system) + return evaluable.prependaxes(index, args.points_shape) -class _TransformsCoords(Array): +class _CoordsAtCoordSystem(Array): - def __init__(self, space: str, transforms: Transforms) -> None: - self._space = space - self._transforms = transforms - super().__init__((transforms.fromdims,), float, frozenset({space}), {}) + def __init__(self, spaces: Tuple[str, ...], coord_system: CoordSystem) -> None: + self._spaces = spaces + self._coord_system = coord_system + super().__init__((coord_system.dim,), float, frozenset(spaces), {}) def lower(self, args: LowerArgs) -> evaluable.Array: - transforms, tip_index = args.transform_chains[self._space] - relative = transforms[0].relative_to(self._transforms) - index = evaluable.TransformsApplyIndex(relative, tip_index) - L = evaluable.TransformsBasis(self._transforms, index)[:,:self._transforms.fromdims] - if self._transforms.todims > self._transforms.fromdims: - LTL = evaluable.einsum('ki,kj->ij', L, L) - Linv = evaluable.einsum('ik,jk->ij', evaluable.inverse(LTL), L) - else: - Linv = evaluable.inverse(L) - Linv = evaluable.prependaxes(Linv, args.points_shape) - tip_coords = args.coordinates[self._space] - tip_coords = evaluable.WithDerivative(tip_coords, _tip_derivative_target(self._space, tip_coords.shape[-1]), evaluable.Diagonalize(evaluable.ones(tip_coords.shape))) - coords = evaluable.TransformsApplyCoords(relative, tip_index, tip_coords) - return evaluable.WithDerivative(coords, _root_derivative_target(self._space, self._transforms.todims), Linv) + index, coords = args.index_coords_at(self._spaces, self._coord_system) + assert coords is not None + return coords class _Derivative(Array): @@ -1019,10 +1140,6 @@ def lower(self, args: LowerArgs) -> evaluable.Array: return evaluable.derivative(arg, self._eval_var) -def _tip_derivative_target(space: str, dim: int) -> evaluable.DerivativeTargetBase: - return evaluable.IdentifierDerivativeTarget((space, 'tip'), (dim,)) - - def _root_derivative_target(space: str, dim: int) -> evaluable.DerivativeTargetBase: return evaluable.IdentifierDerivativeTarget((space, 'root'), (dim,)) @@ -1042,10 +1159,11 @@ def __init__(self, func: Array, geom: Array) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: func = self._func.lower(args) geom = self._geom.lower(args) - ref_dim = builtins.sum(args.transform_chains[space][0][0].todims for space in self._geom.spaces) - if self._geom.shape[-1] != ref_dim: - raise Exception('cannot invert {}x{} jacobian'.format(self._geom.shape[-1], ref_dim)) - refs = tuple(_root_derivative_target(space, chain.todims) for space, ((chain, *_), index) in args.transform_chains.items() if space in self._geom.spaces) + ref_dims = {space: coord_system.dim for space, coord_system in args.reference.items() if space in self._geom.spaces} + sum_ref_dims = builtins.sum(ref_dims.values()) + if self._geom.shape[-1] != sum_ref_dims: + raise Exception('cannot invert {}x{} jacobian'.format(self._geom.shape[-1], sum_ref_dims)) + refs = tuple(_root_derivative_target(space, dim) for space, dim in ref_dims.items()) dfunc_dref = evaluable.concatenate([evaluable.derivative(func, ref) for ref in refs], axis=-1) dgeom_dref = evaluable.concatenate([evaluable.derivative(geom, ref) for ref in refs], axis=-1) dref_dgeom = evaluable.inverse(dgeom_dref) @@ -1068,10 +1186,11 @@ def __init__(self, func: Array, geom: Array) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: func = self._func.lower(args) geom = self._geom.lower(args) - ref_dim = builtins.sum(args.transform_chains[space][0][0].fromdims for space in self._geom.spaces) - if self._geom.shape[-1] != ref_dim + 1: - raise ValueError('expected a {}d geometry but got a {}d geometry'.format(ref_dim + 1, self._geom.shape[-1])) - refs = tuple((_root_derivative_target if chain.todims == chain.fromdims else _tip_derivative_target)(space, chain.fromdims) for space, ((chain, *_), index) in args.transform_chains.items() if space in self._geom.spaces) + ref_dims = {space: coord_system.dim for bound in args.bounds for space, coord_system in bound.reference.items() if space in self._geom.spaces} + sum_ref_dims = builtins.sum(ref_dims.values()) + if self._geom.shape[-1] != sum_ref_dims + 1: + raise ValueError('expected a {}d geometry but got a {}d geometry'.format(sum_ref_dims + 1, self._geom.shape[-1])) + refs = tuple(_root_derivative_target(space, dim) for space, dim in ref_dims.items()) dfunc_dref = evaluable.concatenate([evaluable.derivative(func, ref) for ref in refs], axis=-1) dgeom_dref = evaluable.concatenate([evaluable.derivative(geom, ref) for ref in refs], axis=-1) dref_dgeom = evaluable.einsum('Ajk,Aik->Aij', dgeom_dref, evaluable.inverse(evaluable.grammium(dgeom_dref))) @@ -1096,15 +1215,22 @@ def __init__(self, geom: Array, tip_dim: Optional[int] = None) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: geom = self._geom.lower(args) - tip_dim = builtins.sum(args.transform_chains[space][0][0].fromdims for space in self._geom.spaces) + + # filter bounds that have at least one space of interest + bounds = tuple(bound for bound in args.bounds if not set(bound.spaces).isdisjoint(self._geom.spaces)) + + bound_spaces = frozenset(space for bound in bounds for space in bound.reference) + if bound_spaces != self._geom.spaces: + raise NotImplementedError('jacobian subset bound spaces') + + tip_dim = builtins.sum(bound.dim for bound in bounds) if self._tip_dim is not None and self._tip_dim != tip_dim: raise ValueError('Expected a tip dimension of {} but got {}.'.format(self._tip_dim, tip_dim)) if self._geom.shape[-1] < tip_dim: raise ValueError('the dimension of the geometry cannot be lower than the dimension of the tip coords') if not self._geom.spaces: return evaluable.ones(geom.shape[:-1]) - tips = [_tip_derivative_target(space, chain.fromdims) for space, ((chain, *_), index) in args.transform_chains.items() if space in self._geom.spaces] - J = evaluable.concatenate([evaluable.derivative(geom, tip) for tip in tips], axis=-1) + J = evaluable.concatenate([evaluable.derivative(geom, bound._tip_derivative_target) for bound in bounds], axis=-1) return evaluable.sqrt_abs_det_gram(J) @@ -1117,8 +1243,16 @@ def __init__(self, geom: Array) -> None: def lower(self, args: LowerArgs) -> evaluable.Array: geom = self._geom.lower(args) - spaces_dim = builtins.sum(args.transform_chains[space][0][0].todims for space in self._geom.spaces) - normal_dim = spaces_dim - builtins.sum(args.transform_chains[space][0][0].fromdims for space in self._geom.spaces) + + # filter bounds that have at least one space of interest + bounds = tuple(bound for bound in args.bounds if not set(bound.spaces).isdisjoint(self._geom.spaces)) + + bound_spaces = frozenset(space for bound in bounds for space in bound.reference) + if bound_spaces != self._geom.spaces: + raise NotImplementedError('normal subset bound spaces') + + spaces_dim = builtins.sum(reference.dim for bound in bounds for reference in bound.reference.values()) + normal_dim = spaces_dim - builtins.sum(bound.dim for bound in bounds) if self._geom.shape[-1] != spaces_dim: raise ValueError('The dimension of geometry must equal the sum of the dimensions of the given spaces.') if normal_dim == 0: @@ -1127,18 +1261,18 @@ def lower(self, args: LowerArgs) -> evaluable.Array: raise ValueError('Cannot unambiguously compute the normal because the dimension of the normal space is larger than one.') tangents = [] normal = None - for space, ((chain, *_), index) in args.transform_chains.items(): - if space not in self._geom.spaces: - continue - rgrad = evaluable.derivative(geom, _root_derivative_target(space, chain.todims)) - if chain.todims == chain.fromdims: - # `chain.basis` is `eye(chain.todims)` + for bound in bounds: + rgrad = evaluable.concatenate([evaluable.derivative(geom, _root_derivative_target(space, reference.dim)) for space, reference in bound.reference.items()], axis=-1) + if bound.dim == builtins.sum(reference.dim for reference in bound.reference.values()): + # `trans.basis` is `eye(chain.todims)` tangents.append(rgrad) else: - assert normal is None and chain.todims == chain.fromdims + 1 - basis = evaluable.einsum('Aij,jk->Aik', rgrad, evaluable.TransformsBasis(chain, index)) - tangents.append(basis[..., :chain.fromdims]) - normal = basis[..., chain.fromdims:] + reference = util.product(bound.reference.values()) + trans = bound.coord_systems[0].trans_to(reference) + assert normal is None and trans.to_dim == trans.from_dim + 1 + basis = evaluable.einsum('Aij,jk->Aik', rgrad, evaluable.TransformBasis(trans, bound.index)) + tangents.append(basis[..., :trans.from_dim]) + normal = basis[..., trans.from_dim:] assert normal is not None return evaluable.Normal(evaluable.concatenate((*tangents, normal), axis=-1)) @@ -1151,6 +1285,7 @@ def __init__(self, rgrad: Array) -> None: super().__init__(rgrad.shape[:-1], float, rgrad.spaces, rgrad.arguments) def lower(self, args: LowerArgs) -> evaluable.Array: + raise NotImplemented rgrad = self._rgrad.lower(args) if self._rgrad.shape[-2] == 2: normal = evaluable.stack([rgrad[..., 1, 0], -rgrad[..., 0, 0]], axis=-1) @@ -3299,17 +3434,12 @@ def isarray(__arg: Any) -> bool: return isinstance(__arg, Array) -def rootcoords(space: str, __dim: int) -> Array: - 'Return the root coordinates.' - return _RootCoords(space, __dim) - - -def transforms_index(space: str, transforms: Transforms) -> Array: - return _TransformsIndex(space, transforms) +def transforms_index(coord_system: CoordSystem, *spaces: str) -> Array: + return _IndexAtCoordSystem(spaces, coord_system) -def transforms_coords(space: str, transforms: Transforms) -> Array: - return _TransformsCoords(space, transforms) +def transforms_coords(coord_system: CoordSystem, *spaces: str) -> Array: + return _CoordsAtCoordSystem(spaces, coord_system) def Elemwise(__data: Sequence[numpy.ndarray], __index: IntoArray, dtype: DType) -> Array: diff --git a/nutils/sample.py b/nutils/sample.py index d20c8ae90..2b146c75f 100644 --- a/nutils/sample.py +++ b/nutils/sample.py @@ -17,8 +17,9 @@ from . import types, points, util, function, evaluable, parallel, numeric, matrix, sparse, warnings from .pointsseq import PointsSequence -from .transformseq import Transforms -from typing import Iterable, Mapping, Optional, Sequence, Tuple, Union +from .types import frozendict +from ._rust import CoordSystem +from typing import Dict, Iterable, Mapping, Optional, Sequence, Tuple, Union import numpy import numbers import collections.abc @@ -26,10 +27,6 @@ import treelog as log import abc -_PointsShape = Tuple[evaluable.Array, ...] -_TransformChainsMap = Mapping[str, Tuple[Tuple[Transforms, ...], int]] -_CoordinatesMap = Mapping[str, evaluable.Array] - def argdict(arguments) -> Mapping[str, numpy.ndarray]: if len(arguments) == 1 and 'arguments' in arguments and isinstance(arguments['arguments'], collections.abc.Mapping): @@ -58,14 +55,15 @@ class Sample(types.Singleton): __slots__ = 'spaces', 'ndims', 'nelems', 'npoints' @staticmethod - def new(space: str, transforms: Iterable[Transforms], points: PointsSequence, index: Optional[Union[numpy.ndarray, Sequence[numpy.ndarray]]] = None) -> 'Sample': + def new(reference: Dict[str, CoordSystem], coord_systems: Iterable[CoordSystem], points: PointsSequence, index: Optional[Union[numpy.ndarray, Sequence[numpy.ndarray]]] = None) -> 'Sample': '''Create a new :class:`Sample`. Parameters ---------- - transforms : :class:`tuple` or transformation chains - List of transformation chains leading to local coordinate systems that - contain points. + reference : :class:`dict` of :class:`str` to :class:`~nutils._rust.CoordSystem` + Reference coordinate systems. + coord_systems : :class:`tuple` of :class:`~nutils._rust.CoordSystem` + List of coordinate systems. points : :class:`~nutils.pointsseq.PointsSequence` Points sequence. index : integer array or :class:`tuple` of integer arrays, optional @@ -73,7 +71,7 @@ def new(space: str, transforms: Iterable[Transforms], points: PointsSequence, in If absent the indices will be strictly increasing. ''' - sample = _DefaultIndex(space, tuple(transforms), points) + sample = _DefaultIndex(frozendict(reference), tuple(coord_systems), points) if index is not None: if isinstance(index, (tuple, list)): assert all(ind.shape == (pnt.npoints,) for ind, pnt in zip(index, points)) @@ -82,8 +80,8 @@ def new(space: str, transforms: Iterable[Transforms], points: PointsSequence, in return sample @staticmethod - def empty(spaces: Tuple[str, ...], ndims: int) -> 'Sample': - return _Empty(spaces, ndims) + def empty(reference: Dict[str, CoordSystem], ndims: int) -> 'Sample': + return _Empty(reference, ndims) def __init__(self, spaces: Tuple[str, ...], ndims: int, nelems: int, npoints: int) -> None: ''' @@ -370,41 +368,29 @@ def zip(*samples: 'Sample') -> 'Sample': class _TransformChainsSample(Sample): - __slots__ = 'space', 'transforms', 'points' - - def __init__(self, space: str, transforms: Tuple[Transforms, ...], points: PointsSequence) -> None: - ''' - parameters - ---------- - space : ::class:`str` - The name of the space on which this sample is defined. - transforms : :class:`tuple` or transformation chains - List of transformation chains leading to local coordinate systems that - contain points. - points : :class:`~nutils.pointsseq.PointsSequence` - Points sequence. - ''' + __slots__ = 'reference', 'coord_systems', 'points' - assert len(transforms) >= 1 - assert all(len(t) == len(points) for t in transforms) - self.space = space - self.transforms = transforms + def __init__(self, reference, coord_systems: Tuple[CoordSystem, ...], points: PointsSequence) -> None: + assert len(coord_systems) >= 1 + assert all(len(t) == len(points) for t in coord_systems) + self.reference = reference + self.coord_systems = coord_systems self.points = points - super().__init__((space,), transforms[0].fromdims, len(points), points.npoints) + super().__init__(tuple(reference), coord_systems[0].dim, len(points), points.npoints) def get_evaluable_weights(self, __ielem: evaluable.Array) -> evaluable.Array: return self.points.get_evaluable_weights(__ielem) def get_lower_args(self, __ielem: evaluable.Array) -> function.LowerArgs: - return function.LowerArgs.for_space(self.space, self.transforms, __ielem, self.points.get_evaluable_coords(__ielem)) + return function.Bound(dict(self.reference), self.coord_systems, __ielem, self.points.get_evaluable_coords(__ielem)).into_lower_args() def basis(self) -> function.Array: return _Basis(self) def subset(self, mask: numpy.ndarray) -> Sample: selection = types.frozenarray([ielem for ielem in range(self.nelems) if mask[self.getindex(ielem)].any()]) - transforms = tuple(transform[selection] for transform in self.transforms) - return Sample.new(self.space, transforms, self.points.take(selection)) + coord_systems = tuple(transform[selection] for transform in self.coord_systems) + return Sample.new(self.reference, coord_systems, self.points.take(selection)) def get_element_tri(self, ielem: int) -> numpy.ndarray: if not 0 <= ielem < self.nelems: @@ -456,7 +442,7 @@ def __init__(self, parent: Sample, index: numpy.ndarray) -> None: assert index.shape == (parent.npoints,) self._parent = parent self._index = index - super().__init__(parent.space, parent.transforms, parent.points) + super().__init__(parent.reference, parent.coord_systems, parent.points) def getindex(self, ielem: int) -> numpy.ndarray: return numpy.take(self._index, self._parent.getindex(ielem)) @@ -492,11 +478,11 @@ def get_lower_args(self, __ielem: evaluable.Array) -> function.LowerArgs: raise SkipTest('`{}` does not implement `Sample.get_lower_args`'.format(type(self).__qualname__)) @property - def transforms(self) -> Tuple[Transforms, ...]: - raise SkipTest('`{}` does not implement `Sample.transforms`'.format(type(self).__qualname__)) + def coord_systems(self) -> Tuple[CoordSystem, ...]: + raise SkipTest('`{}` does not implement `Sample.coord_systems`'.format(type(self).__qualname__)) @property - def points(self) -> Tuple[Transforms, ...]: + def points(self) -> Tuple[CoordSystem, ...]: raise SkipTest('`{}` does not implement `Sample.points`'.format(type(self).__qualname__)) def basis(self) -> function.Array: @@ -521,7 +507,7 @@ def get_evaluable_weights(self, __ielem: evaluable.Array) -> evaluable.Array: return evaluable.Zeros((0,) * len(self.spaces), dtype=float) def get_lower_args(self, __ielem: evaluable.Array) -> function.LowerArgs: - return function.LowerArgs((), {}, {}) + return function.LowerArgs((), ()) def get_element_tri(self, ielem: int) -> numpy.ndarray: raise IndexError('index out of range') @@ -718,14 +704,14 @@ def _getslice(self, array, ielem): def get_lower_args(self, __ielem: evaluable.Array) -> function.LowerArgs: points_shape = evaluable.Take(self._sizes, __ielem), - coordinates = {} - transform_chains = {} + bounds = [] for samplei, ielemsi, ilocalsi in zip(self._samples, self._ielems, self._ilocals): argsi = samplei.get_lower_args(evaluable.Take(ielemsi, __ielem)) slicei = self._getslice(ilocalsi, __ielem) - transform_chains.update(argsi.transform_chains) - coordinates.update({space: evaluable._take(coords, slicei, axis=0) for space, coords in argsi.coordinates.items()}) - return function.LowerArgs(points_shape, transform_chains, coordinates) + for bound in argsi.bounds: + coords = evaluable._take(bound.coords, slicei, axis=0) + bounds.append(function.Bound(bound.spaces, bound.coord_systems, bound.index, coords)) + return function.LowerArgs(points_shape, bounds) def get_evaluable_indices(self, ielem): return self._getslice(self._indices, ielem) @@ -912,7 +898,7 @@ def __init__(self, sample: _TransformChainsSample) -> None: super().__init__(shape=(sample.npoints,), dtype=float, spaces=frozenset({sample.space}), arguments={}) def lower(self, args: function.LowerArgs) -> evaluable.Array: - aligned_space_coords = args.coordinates[self._sample.space] + aligned_space_coords = args.get_coords(self._sample.space) assert aligned_space_coords.ndim == len(args.points_shape) + 1 space_coords, where = evaluable.unalign(aligned_space_coords) # Reinsert the coordinate axis, the last axis of `aligned_space_coords`, or @@ -924,10 +910,8 @@ def lower(self, args: function.LowerArgs) -> evaluable.Array: space_coords = evaluable.Transpose(space_coords, numpy.argsort(where)) where = tuple(sorted(where)) - (chain, *_), tip_index = args.transform_chains[self._sample.space] - relative = chain.relative_to(self._sample.transforms[0]) - index = evaluable.TransformsApplyIndex(relative, tip_index) - coords = evaluable.TransformsApplyCoords(relative, tip_index, space_coords) + # FIXME + index, coords = args.relative_index_coords(self._sample.spaces, self._sample.coord_systems[0]) expect = self._sample.points.get_evaluable_coords(index) sampled = evaluable.Sampled(coords, expect) indices = self._sample.get_evaluable_indices(index) diff --git a/src/lib.rs b/src/lib.rs index de2e8471b..d308655b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,7 @@ pub mod simplex; mod util; use map::relative::RelativeTo; -use map::tesselation::Tesselation; -use map::transforms::Transforms; +use map::coord_system::CoordSystem; use map::Map; use numpy::{IntoPyArray, IxDyn, PyArray, PyArrayDyn, PyReadonlyArrayDyn, PyReadonlyArray2, PyArray2}; use pyo3::exceptions::{PyIndexError, PyValueError}; @@ -120,16 +119,15 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { Ok((index, result)) } - #[pyclass(name = "Tesselation", module = "nutils._rust")] + #[pyclass(name = "CoordSystem", module = "nutils._rust")] #[derive(Debug, Clone)] - struct PyTesselation(Tesselation); + struct PyCoordSystem(CoordSystem); #[pymethods] - impl PyTesselation { - #[staticmethod] - pub fn new_identity(shapes: Vec, len: usize) -> Self { - let shapes = shapes.iter().map(|shape| shape.into()).collect(); - Tesselation::identity(shapes, len).into() + impl PyCoordSystem { + #[new] + pub fn new(dim: usize, len: usize) -> Self { + CoordSystem::new(dim, len).into() } pub fn __repr__(&self) -> String { format!("{:?}", self.0) @@ -139,154 +137,12 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { } #[getter] pub fn dim(&self) -> usize { - self.0.len() - } - pub fn __mul__(&self, rhs: &PyTesselation) -> Self { - Self(&self.0 * &rhs.0) - } - pub fn concat(&self, other: &PyTesselation) -> PyResult { - Ok(Self(self.0.concat(&other.0)?)) - } - pub fn take(&self, indices: Vec) -> PyResult { - Ok(Self(self.0.take(&indices)?)) - } - #[getter] - pub fn children(&self) -> Self { - Self(self.0.children()) - } - #[getter] - pub fn edges(&self) -> PyResult { - Ok(Self(self.0.edges()?)) - } - #[getter] - pub fn centroids(&self) -> Self { - Self(self.0.centroids()) - } - #[getter] - pub fn vertices(&self) -> Self { - Self(self.0.vertices()) - } - pub fn apply_index(&self, index: usize) -> PyResult { - self.0 - .apply_index(index) - .ok_or(PyIndexError::new_err("index out of range")) - } - pub fn apply<'py>( - &self, - py: Python<'py>, - index: usize, - coords: PyReadonlyArrayDyn, - ) -> PyResult<(usize, &'py PyArrayDyn)> { - apply_map_from_numpy(py, &self.0, index, coords) - } - //pub fn unapply_indices(&self, indices: Vec) -> PyResult> { - // self.0 - // .unapply_indices(&indices) - // .map(|mut indices| { indices.sort(); indices }) - // .ok_or(PyValueError::new_err("index out of range")) - //} - pub fn relative_to(&self, target: &Self) -> PyResult { - self.0 - .relative_to(&target.0) - .map(|rel| PyMap(rel)) - .ok_or(PyValueError::new_err("cannot make relative")) - } - } - - impl From for PyTesselation { - fn from(tesselation: Tesselation) -> PyTesselation { - PyTesselation(tesselation) - } - } - - m.add_class::()?; - - #[pyclass(name = "Map", module = "nutils._rust")] - #[derive(Debug, Clone)] - struct PyMap(>::Output); - - #[pymethods] - impl PyMap { - pub fn __repr__(&self) -> String { - format!("{:?}", self.0) - } - pub fn len_out(&self) -> usize { - self.0.len_out() - } - pub fn len_in(&self) -> usize { - self.0.len_in() - } - pub fn dim_out(&self) -> usize { - self.0.dim_out() - } - pub fn dim_in(&self) -> usize { - self.0.dim_in() - } - pub fn delta_dim(&self) -> usize { - self.0.delta_dim() - } - pub fn apply_index(&self, index: usize) -> PyResult { - self.0 - .apply_index(index) - .ok_or(PyIndexError::new_err("index out of range")) - } - pub fn apply<'py>( - &self, - py: Python<'py>, - index: usize, - coords: PyReadonlyArrayDyn, - ) -> PyResult<(usize, &'py PyArrayDyn)> { - apply_map_from_numpy(py, &self.0, index, coords) - } - pub fn unapply_indices(&self, indices: Vec) -> PyResult> { - self.0 - .unapply_indices(&indices) - .map(|mut indices| { - indices.sort(); - indices - }) - .ok_or(PyValueError::new_err("index out of range")) - } - } - - m.add_class::()?; - - #[pyclass(name = "Transforms", module = "nutils._rust")] - #[derive(Debug, Clone)] - struct PyTransforms(Transforms); - - #[pymethods] - impl PyTransforms { - #[staticmethod] - pub fn new_identity(dim: usize, len: usize) -> Self { - Transforms::identity(dim, len).into() - } - pub fn __repr__(&self) -> String { - format!("{:?}", self.0) - } - pub fn __len__(&self) -> usize { - self.0.len_in() - } - #[getter] - pub fn fromlen(&self) -> usize { - self.0.len_out() - } - #[getter] - pub fn tolen(&self) -> usize { - self.0.len_out() - } - #[getter] - pub fn fromdims(&self) -> usize { - self.0.dim_in() - } - #[getter] - pub fn todims(&self) -> usize { - self.0.dim_out() + self.0.dim() } - pub fn __mul__(&self, rhs: &PyTransforms) -> Self { + pub fn __mul__(&self, rhs: &PyCoordSystem) -> Self { Self(&self.0 * &rhs.0) } - pub fn concat(&self, other: &PyTransforms) -> PyResult { + pub fn concat(&self, other: &PyCoordSystem) -> PyResult { Ok(Self(self.0.concat(&other.0)?)) } pub fn take(&self, indices: Vec) -> PyResult { @@ -307,67 +163,28 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { let points: Vec = points.as_array().iter().cloned().collect(); Ok(Self(self.0.uniform_points(points, point_dim, offset)?)) } - pub fn apply_index(&self, index: usize) -> PyResult { - self.0 - .apply_index(index) - .ok_or(PyIndexError::new_err("index out of range")) - } - pub fn apply<'py>( - &self, - py: Python<'py>, - index: usize, - coords: PyReadonlyArrayDyn, - ) -> PyResult<(usize, &'py PyArrayDyn)> { - apply_map_from_numpy(py, &self.0, index, coords) - } - //pub fn unapply_indices(&self, indices: Vec) -> PyResult> { - // self.0 - // .unapply_indices(&indices) - // .map(|mut indices| { indices.sort(); indices }) - // .ok_or(PyValueError::new_err("index out of range")) - //} - pub fn relative_to(&self, target: &Self) -> PyResult { + pub fn trans_to(&self, target: &Self) -> PyResult { self.0 .relative_to(&target.0) - .map(|rel| PyRelativeTransforms(rel)) + .map(|rel| PyCoordTrans(rel)) .ok_or(PyValueError::new_err("cannot make relative")) } - pub fn basis<'py>(&self, py: Python<'py>, index: usize) -> PyResult<&'py PyArray2> { - if index >= self.0.len_in() { - return Err(PyIndexError::new_err("index out of range")); - } - let mut basis: Vec = iter::repeat(0.0).take(self.0.dim_out() * self.0.dim_out()).collect(); - for i in 0..self.0.dim_in() { - basis[i * self.0.dim_out() + i] = 1.0; - } - let mut dim_in = self.0.dim_in(); - self.0.update_basis(index, &mut basis[..], self.0.dim_out(), &mut dim_in, 0); - PyArray::from_vec(py, basis).reshape([self.0.dim_out(), self.0.dim_out()]) - } - #[getter] - pub fn is_identity(&self) -> bool { - self.0.is_identity() - } - #[getter] - pub fn is_index_map(&self) -> bool { - self.0.is_index_map() - } } - impl From for PyTransforms { - fn from(transforms: Transforms) -> PyTransforms { - PyTransforms(transforms) + impl From for PyCoordSystem { + fn from(transforms: CoordSystem) -> PyCoordSystem { + PyCoordSystem(transforms) } } - m.add_class::()?; + m.add_class::()?; - #[pyclass(name = "RelativeTransforms", module = "nutils._rust")] + #[pyclass(name = "CoordTrans", module = "nutils._rust")] #[derive(Debug, Clone)] - struct PyRelativeTransforms(>::Output); + struct PyCoordTrans(>::Output); #[pymethods] - impl PyRelativeTransforms { + impl PyCoordTrans { pub fn __repr__(&self) -> String { format!("{:?}", self.0) } @@ -375,19 +192,19 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { self.0.len_in() } #[getter] - pub fn fromlen(&self) -> usize { + pub fn from_len(&self) -> usize { self.0.len_out() } #[getter] - pub fn tolen(&self) -> usize { + pub fn to_len(&self) -> usize { self.0.len_out() } #[getter] - pub fn fromdims(&self) -> usize { + pub fn from_dim(&self) -> usize { self.0.dim_in() } #[getter] - pub fn todims(&self) -> usize { + pub fn to_dim(&self) -> usize { self.0.dim_out() } pub fn apply_index(&self, index: usize) -> PyResult { @@ -434,7 +251,7 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { } } - m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/src/map/coord_system.rs b/src/map/coord_system.rs new file mode 100644 index 000000000..845e0fde6 --- /dev/null +++ b/src/map/coord_system.rs @@ -0,0 +1,310 @@ +use super::ops::UniformConcat; +use super::primitive::{ + AllPrimitiveDecompositions, Primitive, PrimitiveDecompositionIter, UnboundedMap, WithBounds, +}; +use super::relative::RelativeTo; +use super::{AddOffset, Error, Map, UnapplyIndicesData}; +use crate::simplex::Simplex; +use crate::util::{ReplaceNthIter, SkipNthIter}; +use std::iter; +use std::ops::Mul; +use std::sync::Arc; + +#[derive(Debug, Clone, PartialEq)] +pub struct UniformCoordSystem(WithBounds>); + +impl UniformCoordSystem { + pub fn new(dim: usize, len: usize) -> Self { + Self(WithBounds::new_unchecked(Vec::new(), dim, len)) + } + pub fn clone_and_push(&self, primitive: Primitive) -> Result { + if self.0.dim_in() < primitive.dim_out() { + return Err(Error::DimensionMismatch); + } + if self.0.len_in() % primitive.mod_out() != 0 { + return Err(Error::LengthMismatch); + } + let dim_in = self.0.dim_in() - primitive.delta_dim(); + let len_in = self.0.len_in() / primitive.mod_out() * primitive.mod_in(); + let map = self + .0 + .unbounded() + .iter() + .cloned() + .chain(iter::once(primitive)) + .collect(); + Ok(Self(WithBounds::new_unchecked(map, dim_in, len_in))) + } + fn mul(&self, rhs: &Self) -> Self { + let offset = self.0.dim_in(); + let map = iter::once(Primitive::new_transpose(rhs.0.len_out(), self.0.len_out())) + .chain(self.0.unbounded().iter().cloned()) + .chain(iter::once(Primitive::new_transpose( + self.0.len_in(), + rhs.0.len_out(), + ))) + .chain(rhs.0.unbounded().iter().map(|item| { + let mut item = item.clone(); + item.add_offset(offset); + item + })) + .collect(); + Self(WithBounds::new_unchecked( + map, + self.0.dim_in() + rhs.0.dim_in(), + self.0.len_in() * rhs.0.len_in(), + )) + } +} + +//impl Deref for UniformCoordSystem { +// type Target = WithBounds>; +// +// fn deref(&self) -> Self::Target { +// self.map +// } +//} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $(-> $ret:ty)? + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { + $self.map.$fn($($arg),*) + } + }; +} + +impl Map for UniformCoordSystem { + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Result} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} + dispatch! {fn is_index_map(&self) -> bool} + dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} +} + +impl AllPrimitiveDecompositions for UniformCoordSystem { + fn all_primitive_decompositions<'a>(&'a self) -> PrimitiveDecompositionIter<'a, Self> { + Box::new( + self.0 + .all_primitive_decompositions() + .map(|(prim, map)| (prim, Self(map))), + ) + } +} + +impl RelativeTo for UniformCoordSystem { + type Output = > as RelativeTo>>>::Output; + + fn relative_to(&self, target: &Self) -> Option { + self.0.relative_to(&target.0) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CoordSystem(UniformConcat); + +impl CoordSystem { + pub fn new(dim: usize, len: usize) -> Self { + let identity = UniformCoordSystem::new(dim, len); + Self(UniformConcat::new_unchecked(vec![identity], dim, 0, len)) + } + fn mul(&self, rhs: &Self) -> Self { + let products = self + .0 + .iter() + .flat_map(|lhs| rhs.0.iter().map(move |rhs| lhs * rhs)) + .collect(); + CoordSystem(UniformConcat::new_unchecked( + products, + self.dim_in() + rhs.dim_in(), + self.delta_dim() + rhs.delta_dim(), + self.len_out() * rhs.len_out(), + )) + } + pub fn len(&self) -> usize { + self.len_in() + } + pub fn dim(&self) -> usize { + self.dim_in() + } + pub fn concat(&self, other: &Self) -> Result { + let maps = self.0.iter().chain(other.0.iter()).cloned().collect(); + Ok(Self(UniformConcat::new( + maps, + self.dim_in(), + self.delta_dim(), + self.len_out(), + )?)) + } + pub fn take(&self, indices: &[usize]) -> Result { + if !indices.windows(2).all(|pair| pair[0] < pair[1]) { + return Err(Error::IndicesNotStrictIncreasing); + } + if let Some(last) = indices.last() { + if *last >= self.0.len_in() { + return Err(Error::IndexOutOfRange); + } + } + let mut maps = Vec::new(); + let mut offset = 0; + let mut start = 0; + for map in self.0.iter() { + let stop = start + indices[start..].partition_point(|i| *i < offset + map.len_in()); + if stop > start { + let map_indices: Vec<_> = + indices[start..stop].iter().map(|i| *i - offset).collect(); + start = stop; + let primitive = Primitive::new_take(map_indices, map.len_in()); + maps.push(map.clone_and_push(primitive).unwrap()); + } + offset += map.len_in(); + } + assert_eq!(start, indices.len()); + Ok(Self(UniformConcat::new_unchecked( + maps, + self.dim_in(), + self.delta_dim(), + self.len_out(), + ))) + } + fn clone_and_push(&self, primitive: Primitive) -> Result { + if self.0.dim_in() < primitive.dim_out() { + return Err(Error::DimensionMismatch); + } + if self.0.len_in() % primitive.mod_out() != 0 { + return Err(Error::LengthMismatch); + } + let maps = self + .0 + .iter() + .map(|map| map.clone_and_push(primitive.clone()).unwrap()); + Ok(Self(UniformConcat::new_unchecked( + maps.collect(), + self.dim_in() - primitive.delta_dim(), + self.delta_dim() + primitive.delta_dim(), + self.len_out(), + ))) + } + pub fn children(&self, simplex: Simplex, offset: usize) -> Result { + self.clone_and_push(Primitive::new_children(simplex).with_offset(offset)) + } + pub fn edges(&self, simplex: Simplex, offset: usize) -> Result { + self.clone_and_push(Primitive::new_edges(simplex).with_offset(offset)) + } + pub fn uniform_points( + &self, + points: impl Into>, + point_dim: usize, + offset: usize, + ) -> Result { + self.clone_and_push(Primitive::new_uniform_points(points, point_dim).with_offset(offset)) + } +} + +//impl Deref for CoordSystem { +// type Target = OptionReorder>, Vec>; +// +// fn deref(&self) -> Self::Target { +// self.0 +// } +//} + +macro_rules! dispatch { + ( + $vis:vis fn $fn:ident$(<$genarg:ident: $genpath:path>)?( + &$self:ident $(, $arg:ident: $ty:ty)* + ) $(-> $ret:ty)? + ) => { + #[inline] + $vis fn $fn$(<$genarg: $genpath>)?(&$self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; + ($vis:vis fn $fn:ident(&mut $self:ident $(, $arg:ident: $ty:ty)*) $(-> $ret:ty)?) => { + #[inline] + $vis fn $fn(&mut $self $(, $arg: $ty)*) $(-> $ret)? { + $self.0.$fn($($arg),*) + } + }; +} + +impl Map for CoordSystem { + dispatch! {fn len_out(&self) -> usize} + dispatch! {fn len_in(&self) -> usize} + dispatch! {fn dim_out(&self) -> usize} + dispatch! {fn dim_in(&self) -> usize} + dispatch! {fn delta_dim(&self) -> usize} + dispatch! {fn apply_inplace_unchecked(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> usize} + dispatch! {fn apply_inplace(&self, index: usize, coordinates: &mut [f64], stride: usize, offset: usize) -> Result} + dispatch! {fn apply_index_unchecked(&self, index: usize) -> usize} + dispatch! {fn apply_index(&self, index: usize) -> Option} + dispatch! {fn apply_indices_inplace_unchecked(&self, indices: &mut [usize])} + dispatch! {fn apply_indices(&self, indices: &[usize]) -> Option>} + dispatch! {fn unapply_indices_unchecked(&self, indices: &[T]) -> Vec} + dispatch! {fn unapply_indices(&self, indices: &[T]) -> Option>} + dispatch! {fn is_identity(&self) -> bool} + dispatch! {fn is_index_map(&self) -> bool} + dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} +} + +impl RelativeTo for CoordSystem { + type Output = + as RelativeTo>>::Output; + + fn relative_to(&self, target: &Self) -> Option { + self.0.relative_to(&target.0) + } +} + +impl Mul for &UniformCoordSystem { + type Output = UniformCoordSystem; + + fn mul(self, rhs: Self) -> UniformCoordSystem { + UniformCoordSystem::mul(self, rhs) + } +} + +impl Mul for UniformCoordSystem { + type Output = UniformCoordSystem; + + fn mul(self, rhs: Self) -> UniformCoordSystem { + UniformCoordSystem::mul(&self, &rhs) + } +} + +impl Mul for &CoordSystem { + type Output = CoordSystem; + + fn mul(self, rhs: Self) -> CoordSystem { + CoordSystem::mul(self, rhs) + } +} + +impl Mul for CoordSystem { + type Output = CoordSystem; + + fn mul(self, rhs: Self) -> CoordSystem { + CoordSystem::mul(&self, &rhs) + } +} diff --git a/src/map/mod.rs b/src/map/mod.rs index f184be19f..6b9eda324 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -1,8 +1,7 @@ pub mod ops; pub mod primitive; pub mod relative; -pub mod tesselation; -pub mod transforms; +pub mod coord_system; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { diff --git a/src/map/primitive.rs b/src/map/primitive.rs index 1bd628e75..cf7c29ea5 100644 --- a/src/map/primitive.rs +++ b/src/map/primitive.rs @@ -947,11 +947,11 @@ where } } -/// An interface for swapping a composition of `Self` with an [`Primitive`]. +/// An interface for swapping a composition of `Self` with a [`Primitive`]. pub trait SwapPrimitiveComposition { type Output; - /// Returns an [`Primitive`'] and a map such that the composition those is equivalent to the composition of `self` with `inner`. + /// Returns a [`Primitive`'] and a map such that the composition of those is equivalent to the composition of `self` with `inner`. fn swap_primitive_composition( &self, inner: &Primitive, From 0bc8659137581371a8758e86264d4c0d055cefaa Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Tue, 26 Jul 2022 21:36:22 +0200 Subject: [PATCH 43/45] WIP --- examples/coil.py | 2 +- nutils/elementseq.py | 24 +- nutils/evaluable.py | 25 +- nutils/function.py | 2 +- nutils/mesh.py | 68 +- nutils/topology.py | 1577 +++++++++++++++------------------------ src/finite_f64.rs | 7 + src/lib.rs | 105 +++ src/map/coord_system.rs | 6 +- src/map/mod.rs | 1 + src/map/ops.rs | 33 +- src/map/primitive.rs | 75 +- src/map/relative.rs | 12 +- src/simplex.rs | 2 +- 14 files changed, 878 insertions(+), 1061 deletions(-) diff --git a/examples/coil.py b/examples/coil.py index 0cd6b586d..a50bf0b94 100644 --- a/examples/coil.py +++ b/examples/coil.py @@ -65,7 +65,7 @@ def main(nelems: int = 50, degree: int = 3, freq: float = 0., nturns: int = 1, r # domain is mapped from [0,1] to [0,inf) using an arctanh transform. Finally, # a Neumann boundary condition is used at z=0 to obtain symmetry in z=0. - RZ, ns.rz0 = mesh.rectilinear([numpy.linspace(0, 1, nelems)]*2, space='RZ') + RZ, ns.rz0 = mesh.rectilinear([numpy.linspace(0, 1, nelems)]*2, spaces=('R', 'Z')) REV, ns.θ = mesh.line([-numpy.pi, numpy.pi], bnames=['start', 'end'], space='Θ') REV0 = REV.refined[:1].boundary['end'].sample('bezier', 2) ns.rz = numpy.arctanh(ns.rz0) * 2 * rcoil diff --git a/nutils/elementseq.py b/nutils/elementseq.py index ffd49b3e9..80f96d87a 100644 --- a/nutils/elementseq.py +++ b/nutils/elementseq.py @@ -122,10 +122,7 @@ def __getitem__(self, index): if numeric.isint(index): return self.get(index) elif isinstance(index, slice): - index = range(len(self))[index] - if index == range(len(self)): - return self - return self.take(numpy.arange(index.start, index.stop, index.step)) + return self.slice(index) elif numeric.isintarray(index): return self.take(index) elif numeric.isboolarray(index): @@ -175,6 +172,25 @@ def get(self, index: int) -> Reference: raise NotImplementedError + def slice(self, __s: slice) -> 'References': + '''Return a slice of this sequence. + + Parameters + ---------- + s : :class:`slice` + The slice. + + Returns + ------- + :class:`References` + The slice. + ''' + + start, stop, step = __s.indices(len(self)) + if start == 0 and stop == len(self) and step == 1: + return self + return self.take(numpy.arange(start, stop, step)) + def take(self, indices: numpy.ndarray) -> 'References': '''Return a selection of this sequence. diff --git a/nutils/evaluable.py b/nutils/evaluable.py index c14bf05d0..c681f9e3c 100644 --- a/nutils/evaluable.py +++ b/nutils/evaluable.py @@ -2300,6 +2300,15 @@ class FloorDivide(Pointwise): __slots__ = () evalf = numpy.floor_divide + def _intbounds_impl(self): + dl, du = self.args[1]._intbounds + if dl != du or not isinstance(dl, int) or dl <= 0: + return super()._intbounds_impl() + nl, nu = self.args[0]._intbounds + l = nl // dl if isinstance(nl, int) else nl + u = nu // dl if isinstance(nu, int) else nu + return l, u + class Absolute(Pointwise): __slots__ = () @@ -3883,6 +3892,9 @@ class TransformCoords(Array): def __init__(self, trans, index: Array, coords: Array): if index.dtype != int or index.ndim != 0: raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + imin, imax = index._intbounds + if imin < 0 or imax >= trans.from_len: + raise ValueError('argument `index` is out of bounds') if coords.dtype != float: raise ValueError('argument `coords` must be a real-valued array with at least one axis') self._trans = trans @@ -3908,6 +3920,9 @@ class TransformIndex(Array): def __init__(self, trans, index: Array): if index.dtype != int or index.ndim != 0: raise ValueError('argument `index` must be a scalar, integer `nutils.evaluable.Array`') + imin, imax = index._intbounds + if imin < 0 or imax >= trans.from_len: + raise ValueError('argument `index` is out of bounds') self._trans = trans self._index = index super().__init__(args=[index], shape=(), dtype=int) @@ -3916,12 +3931,16 @@ def evalf(self, index): return numpy.array(self._trans.apply_index(index.__index__())) def _intbounds_impl(self): - return 0, len(self._trans) - 1 + return 0, self._trans.to_len - 1 def _simplified(self): if self._trans.is_identity: return self._index + @property + def _node_details(self): + return super()._node_details + f'\n{hash(self)}' + class TransformBasis(Array): @@ -3934,6 +3953,10 @@ def __init__(self, trans, index: Array): def evalf(self, index): return self._trans.basis(index.__index__()) + def _simplified(self): + if self._trans.basis_is_constant: + return asarray(self._trans.basis(0)) + class _LoopIndex(Argument): diff --git a/nutils/function.py b/nutils/function.py index d6bcd6f4a..1f57aed9c 100644 --- a/nutils/function.py +++ b/nutils/function.py @@ -177,7 +177,7 @@ def index_coords_at(self, spaces: Tuple[str, ...], coord_system: CoordSystem) -> bound_index = bounds[0].index for bound in bounds[1:]: - bound_index = bound_index * len(bound.coord_system) + bound.index + bound_index = bound_index * len(bound.coord_systems[0]) + bound.index index = evaluable.TransformIndex(trans, bound_index) if stride_after != 1: index = evaluable.FloorDivide(index, stride_after) diff --git a/nutils/mesh.py b/nutils/mesh.py index 10d501d2a..2f56a537f 100644 --- a/nutils/mesh.py +++ b/nutils/mesh.py @@ -10,6 +10,7 @@ from .elementseq import References from .transform import TransformItem from .topology import Topology +from ._rust import CoordSystem from typing import Optional, Sequence, Tuple, Union import numpy import os @@ -19,70 +20,32 @@ import treelog as log import io import contextlib +from collections import OrderedDict _ = numpy.newaxis # MESH GENERATORS -@log.withcontext -def rectilinear(richshape: Sequence[Union[int, Sequence[float]]], periodic: Sequence[int] = (), name: Optional[str] = None, space: str = 'X', root: Optional[TransformItem] = None) -> Tuple[Topology, function.Array]: - 'rectilinear mesh' - - verts = [numpy.arange(v + 1) if numeric.isint(v) else v for v in richshape] - shape = [len(v) - 1 for v in verts] - ndims = len(shape) - - if name is not None: - warnings.deprecation('Argument `name` is deprecated; use `root` with a `TransformItem` instead.') - if root is not None: - raise ValueError('Arguments `name` and `root` cannot be used simultaneously.') - root = transform.Index(hash(name)) - elif root is None: - root = transform.Index(ndims, 0) - - axes = [transformseq.DimAxis(i=0, j=n, mod=n if idim in periodic else 0, isperiodic=idim in periodic) for idim, n in enumerate(shape)] - topo = topology.StructuredTopology(space, root, axes) - - funcsp = topo.basis('spline', degree=1, periodic=()) - coords = numeric.meshgrid(*verts).reshape(ndims, -1) - geom = (funcsp * coords).sum(-1) - - return topo, geom - - -_oldrectilinear = rectilinear # reference for internal unittests - - -def line(nodes: Union[int, Sequence[float]], periodic: bool = False, bnames: Optional[Sequence[Tuple[str, str]]] = None, *, name: Optional[str] = None, space: str = 'X', root: Optional[TransformItem] = None) -> Tuple[Topology, function.Array]: - if name is not None: - warnings.deprecation('Argument `name` is deprecated; use `root` with a `transform.transformitem` instead.') - if root is not None: - raise ValueError('Arguments `name` and `root` cannot be used simultaneously.') - root = transform.Index(hash(name)) - elif root is None: - root = transform.Index(1, 0) +def line(nodes: Union[int, Sequence[float]], periodic: bool = False, bnames: Optional[Tuple[str, str]] = None, *, space: str = 'X') -> Tuple[Topology, function.Array]: if isinstance(nodes, int): nodes = numpy.arange(nodes + 1) - domain = topology.StructuredLine(space, root, 0, len(nodes) - 1, periodic=periodic, bnames=bnames) - geom = domain.basis('std', degree=1, periodic=[]).dot(nodes) + domain = Topology.line(space, len(nodes) - 1, periodic=periodic, bnames=bnames) + geom = domain.basis('std', degree=1, periodic=()) @ nodes return domain, geom -def newrectilinear(nodes: Sequence[Union[int, Sequence[float]]], periodic: Optional[Sequence[int]] = None, name: Optional[str] = None, bnames=[['left', 'right'], ['bottom', 'top'], ['front', 'back']], spaces: Optional[Sequence[str]] = None, root: Optional[TransformItem] = None) -> Tuple[Topology, function.Array]: +def rectilinear(nodes: Sequence[Union[int, Sequence[float]]], periodic: Optional[Sequence[int]] = None, bnames=[['left', 'right'], ['bottom', 'top'], ['front', 'back']], spaces: Optional[Sequence[str]] = None) -> Tuple[Topology, function.Array]: if periodic is None: periodic = [] if not spaces: spaces = 'XYZ' if len(nodes) <= 3 else map('R{}'.format, range(len(nodes))) else: assert len(spaces) == len(nodes) - domains, geoms = zip(*(line(nodesi, i in periodic, bnamesi, name=name, space=spacei, root=root) for i, (nodesi, bnamesi, spacei) in enumerate(zip(nodes, tuple(bnames)+(None,)*len(nodes), spaces)))) + domains, geoms = zip(*(line(nodesi, i in periodic, bnamesi, space=spacei) for i, (nodesi, bnamesi, spacei) in enumerate(zip(nodes, tuple(bnames)+(None,)*len(nodes), spaces)))) return util.product(domains), numpy.stack(geoms) -if os.environ.get('NUTILS_TENSORIAL'): - def rectilinear(richshape: Sequence[Union[int, Sequence[float]]], periodic: Sequence[int] = (), name: Optional[str] = None, space: str = 'X', root: Optional[TransformItem] = None) -> Tuple[Topology, function.Array]: - spaces = tuple(space+str(i) for i in range(len(richshape))) - return newrectilinear(richshape, periodic, name=name, spaces=spaces, root=root) +newrectilinear = rectilinear @log.withcontext @@ -637,10 +600,8 @@ def unitsquare(nelems, etype): The geometry function. ''' - space = 'X' - if etype == 'square': - topo, geom = rectilinear([nelems, nelems], space=space) + topo, geom = rectilinear([nelems, nelems]) return topo, geom / nelems elif etype in ('triangle', 'mixed'): @@ -651,7 +612,7 @@ def unitsquare(nelems, etype): v = numpy.arange(nelems+1, dtype=float) coords = numeric.meshgrid(v, v).reshape(2, -1).T transforms = transformseq.IndexTransforms(2, len(simplices)) - topo = topology.SimplexTopology(space, simplices, transforms, transforms) + topo = Topology.simplex('X', simplices) if etype == 'mixed': references = list(topo.references) @@ -665,9 +626,14 @@ def unitsquare(nelems, etype): connectivity[n*2:(n+1)*2] = numpy.concatenate(connectivity[n*2:(n+1)*2])[[3, 2, 4, 1] if i % 2 == j % 2 else [3, 2, 0, 5]], connectivity = [c-numpy.greater(c, n*2) for c in connectivity] dofs[n*2:(n+1)*2] = numpy.unique([*dofs[n*2], *dofs[n*2+1]]), + connectivity = tuple(map(types.frozenarray, connectivity)) coords = coords[numpy.argsort(numpy.unique(numpy.concatenate(dofs), return_index=True)[1])] - transforms = transformseq.IndexTransforms(2, len(connectivity)) - topo = topology.ConnectedTopology(space, References.from_iter(references, 2), transforms, transforms, connectivity) + raise NotImplementedError + # TODO: sort references and use a concatenate + references = References.from_iter(references, 2) + coord_system = CoordSystem(2, len(connectivity)) + ref_coord_system = OrderedDict(X=coord_system) + topo = topology._ConformingTopology(ref_coord_system, references, coord_system, coord_system, connectivity) geom = (topo.basis('std', degree=1) * coords.T).sum(-1) x, y = topo.boundary.sample('_centroid', None).eval(geom).T diff --git a/nutils/topology.py b/nutils/topology.py index c260b2fa7..8c994ee33 100644 --- a/nutils/topology.py +++ b/nutils/topology.py @@ -14,14 +14,18 @@ :mod:`nutils.element` iterators. """ -from . import element, function, evaluable, util, parallel, numeric, cache, transform, transformseq, warnings, matrix, types, points, sparse +from . import element, function, evaluable, util, parallel, numeric, cache, transform, warnings, matrix, types, points, sparse from .sample import Sample +from .element import Reference from .elementseq import References from .pointsseq import PointsSequence -from typing import Any, FrozenSet, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Union +from ._rust import CoordSystem, Simplex +from typing import Any, Dict, FrozenSet, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Union +import typing import numpy import functools import collections.abc +from collections import OrderedDict import itertools import functools import operator @@ -37,15 +41,12 @@ _ArgDict = Mapping[str, numpy.ndarray] -class Topology(types.Singleton): +class Topology: '''topology base class Parameters ---------- - spaces : :class:`tuple` of :class:`str` - The unique, ordered list of spaces on which this topology is defined. - space_dims : :class:`tuple` of :class:`int` - The dimension of each space in :attr:`spaces`. + ref_coord_system : :class:`dict` of :class:`str` to :class:`~nutils._rust.CoordSystem` references : :class:`nutils.elementseq.References` The references. @@ -55,24 +56,22 @@ class Topology(types.Singleton): The unique, ordered list of spaces on which this topology is defined. space_dims : :class:`tuple` of :class:`int` The dimension of each space in :attr:`spaces`. + ref_coord_system : :class:`dict` of :class:`str` to :class:`~nutils._rust.CoordSystem` references : :class:`nutils.elementseq.References` The references. ndims : :class:`int` The dimension of this topology. ''' - __slots__ = 'spaces', 'space_dims', 'references', 'ndims' + __slots__ = 'ref_coord_system', 'spaces', 'space_dims', 'references', 'coord_system', 'opposite', 'ndims' @staticmethod - def empty(spaces: Iterable[str], space_dims: Iterable[int], ndims: int) -> 'Topology': + def empty(ref_coord_system: typing.OrderedDict[str, CoordSystem], ndims: int) -> 'Topology': '''Return an empty topology. Parameters ---------- - spaces : :class:`tuple` of :class:`str` - The unique, ordered list of spaces on which the empty topology is defined. - space_dims : :class:`tuple` of :class:`int` - The dimension of each space in :attr:`spaces`. + ref_coord_system : :class:`collections.OrderedDict` of :class:`str` to :class:`~nutils._rust.CoordSystem` ndims : :class:`int` The dimension of the empty topology. @@ -86,7 +85,7 @@ def empty(spaces: Iterable[str], space_dims: Iterable[int], ndims: int) -> 'Topo :meth:`empty_like` : create an empty topology with spaces and dimension copied from another topology ''' - return _Empty(tuple(spaces), tuple(space_dims), ndims) + return _Empty(ref_coord_system, ndims) def empty_like(self) -> 'Topology': '''Return an empty topology with the same spaces and dimensions as this topology. @@ -101,7 +100,7 @@ def empty_like(self) -> 'Topology': :meth:`empty_like` : create an empty topology with custom spaces and dimension ''' - return Topology.empty(self.spaces, self.space_dims, self.ndims) + return Topology.empty(self.ref_coord_system, self.ndims) def disjoint_union(*topos: 'Topology') -> 'Topology': '''Return the union of the given disjoint topologies. @@ -130,10 +129,47 @@ def disjoint_union(*topos: 'Topology') -> 'Topology': else: return empty - def __init__(self, spaces: Tuple[str, ...], space_dims: Tuple[int, ...], references: References) -> None: - self.spaces = spaces - self.space_dims = space_dims + @staticmethod + def line(space: str, nelems: int, bnames: Optional[Iterable[str]] = None, periodic: bool = False) -> 'Topology': + if not isinstance(space, str): + raise ValueError('argument `space`: expected a `str`') + if not isinstance(nelems, int) or nelems < 0: + raise ValueError('argument `nelems`: expected a non-negative `int`') + if bnames is None: + bnames = f'{space}-left', f'{space}-right' + else: + bnames = tuple(bnames) + if len(bnames) != 2 or not all(isinstance(name, str) for name in bnames): + raise ValueError('argument `bnames`: expected an iterable of two `str`') + coord_system = CoordSystem(1, nelems) + ref_coord_system = OrderedDict({space: coord_system}) + return _Line(ref_coord_system, coord_system, coord_system, bnames, bool(periodic)) + + @staticmethod + def simplex(space: str, simplices: numpy.array) -> 'Topology': + if not isinstance(space, str): + raise ValueError('argument `space`: expected a `str`') + + simplices = numpy.asarray(simplices) + keep = numpy.zeros(simplices.max()+1, dtype=bool) + keep[simplices.flat] = True + simplices = types.arraydata(simplices if keep.all() else (numpy.cumsum(keep)-1)[simplices]) + + coord_system = CoordSystem(simplices.shape[1] - 1, simplices.shape[0]) + ref_coord_system = OrderedDict({space: coord_system}) + return _SimplexTopology(ref_coord_system, simplices, coord_system, coord_system) + + def __init__(self, ref_coord_system: typing.OrderedDict[str, CoordSystem], references: References, coord_system: CoordSystem, opposite: CoordSystem): + if not isinstance(ref_coord_system, OrderedDict): + raise ValueError('argument `ref_coord_system`: expected an `collections.OrderedDict`') + if not (references.ndims == coord_system.dim == opposite.dim and len(references) == len(coord_system) == len(opposite)): + raise ValueError('`references`, `coord_system` and `opposite` have different dimensions') + self.ref_coord_system = ref_coord_system + self.spaces = tuple(ref_coord_system) + self.space_dims = tuple(ref.dim for ref in ref_coord_system.values()) self.references = references + self.coord_system = coord_system + self.opposite = opposite self.ndims = references.ndims super().__init__() @@ -282,42 +318,44 @@ def __getitem__(self, item: Any) -> 'Topology': raise KeyError(item) return topo + def __invert__(self): + return OppositeTopology(self) + def __mul__(self, other: Any) -> 'Topology': if isinstance(other, Topology): - return _Mul(self, other) + left = self._disjoint_topos + right = other._disjoint_topos + empty = Topology.empty(OrderedDict(**self.ref_coord_system, **other.ref_coord_system), self.ndims + other.ndims) + return Topology.disjoint_union(empty, *(_Mul(l, r) for l in left for r in right if len(l) and len(r))) else: return NotImplemented def __and__(self, other: Any) -> 'Topology': if not isinstance(other, Topology): return NotImplemented - elif self.spaces != other.spaces or self.space_dims != other.space_dims or self.ndims != other.ndims: - raise ValueError('The topologies must have the same spaces and dimensions.') + elif self.ref_coord_system != other.ref_coord_system: + raise ValueError('The topologies must have the same (order of) spaces and reference coordinate system.') elif not self or not other: return self.empty_like() else: - return NotImplemented + raise NotImplementedError __rand__ = __and__ def __or__(self, other: Any) -> 'Topology': if not isinstance(other, Topology): return NotImplemented - elif self.spaces != other.spaces or self.space_dims != other.space_dims or self.ndims != other.ndims: - raise ValueError('The topologies must have the same spaces and dimensions.') + elif self.ref_coord_system != other.ref_coord_system: + raise ValueError('The topologies must have the same (order of) spaces and dimensions.') elif not self: return other elif not other: return self else: - return NotImplemented + return UnionTopology((self, other)) __ror__ = __or__ - @property - def border_transforms(self) -> transformseq.Transforms: - raise NotImplementedError - @property def refine_iter(self) -> 'Topology': topo = self @@ -325,17 +363,23 @@ def refine_iter(self) -> 'Topology': yield topo topo = topo.refined + @property + def _index_coords(self): + index = function.transforms_index(self.coord_system, *self.ref_coord_system) + coords = function.transforms_coords(self.coord_system, *self.ref_coord_system) + return index, coords + @property def f_index(self) -> function.Array: '''The evaluable index of the element in this topology.''' - raise NotImplementedError + return self._index_coords[0] @property def f_coords(self) -> function.Array: '''The evaluable element local coordinates.''' - raise NotImplementedError + return self._index_coords[1] def basis(self, name: str, *args, **kwargs) -> function.Basis: ''' @@ -354,7 +398,12 @@ def basis(self, name: str, *args, **kwargs) -> function.Basis: def sample(self, ischeme: str, degree: int) -> Sample: 'Create sample.' - raise NotImplementedError + points = PointsSequence.from_iter((ischeme(reference, degree) for reference in self.references), self.ndims) if callable(ischeme) \ + else self.references.getpoints(ischeme, degree) + coord_systems = self.coord_system, + if len(self.coord_system) == 0 or self.opposite != self.coord_system: + coord_systems += self.opposite, + return Sample.new(self.ref_coord_system, coord_systems, points) @util.single_or_multiple def integrate_elementwise(self, funcs: Iterable[function.Array], *, degree: int, asfunction: bool = False, ischeme: str = 'gauss', arguments: Optional[_ArgDict] = None) -> Union[List[numpy.ndarray], List[function.Array]]: @@ -482,7 +531,7 @@ def project(self, fun: function.Array, onto: function.Array, geometry: function. def refined_by(self, refine: Iterable[int]) -> 'Topology': 'create refined space by refining dofs in existing one' - raise NotImplementedError + return HierarchicalTopology(self, [numpy.arange(len(self))]).refined_by(refine) @property def refined(self) -> 'Topology': @@ -594,7 +643,7 @@ def refine_spaces_unchecked(self, __spaces: FrozenSet[str]) -> 'Topology': doing. ''' - raise NotImplementedError + return RefinedTopology(self) def refine_spaces_count(self, count: Mapping[str, int]) -> 'Topology': '''Return the topology with the given spaces refined the given amount times. @@ -632,16 +681,66 @@ def trim(self, levelset: function.Array, maxrefine: int, ndivisions: int = 8, na raise NotImplementedError + if arguments is None: + arguments = {} + + refs = [] + if leveltopo is None: + ielem_arg = evaluable.Argument('_trim_index', (), dtype=int) + coordinates = self.references.getpoints('vertex', maxrefine).get_evaluable_coords(ielem_arg) + levelset = levelset.lower(function.LowerArgs.for_space(self.space, (self.transforms, self.opposites), ielem_arg, coordinates)).optimized_for_numpy + with log.iter.percentage('trimming', range(len(self)), self.references) as items: + for ielem, ref in items: + levels = levelset.eval(_trim_index=ielem, **arguments) + refs.append(ref.trim(levels, maxrefine=maxrefine, ndivisions=ndivisions)) + else: + log.info('collecting leveltopo elements') + coordinates = evaluable.Points(evaluable.NPoints(), self.ndims) + ielem = evaluable.Argument('_leveltopo_ielem', (), int) + levelset = levelset.lower(function.LowerArgs.for_space(self.space, (leveltopo.transforms, leveltopo.opposites), ielem, coordinates)).optimized_for_numpy + bins = [set() for ielem in range(len(self))] + for trans in leveltopo.transforms: + ielem, tail = self.transforms.index_with_tail(trans) + bins[ielem].add(tail) + fcache = cache.WrapperCache() + with log.iter.percentage('trimming', self.references, self.transforms, bins) as items: + for ref, trans, ctransforms in items: + levels = numpy.empty(ref.nvertices_by_level(maxrefine)) + cover = list(fcache[ref.vertex_cover](frozenset(ctransforms), maxrefine)) + # confirm cover and greedily optimize order + mask = numpy.ones(len(levels), dtype=bool) + while mask.any(): + imax = numpy.argmax([mask[indices].sum() for tail, points, indices in cover]) + tail, points, indices = cover.pop(imax) + ielem = leveltopo.transforms.index(trans + tail) + levels[indices] = levelset.eval(_leveltopo_ielem=ielem, _points=points, **arguments) + mask[indices] = False + refs.append(ref.trim(levels, maxrefine=maxrefine, ndivisions=ndivisions)) + log.debug('cache', fcache.stats) + return SubsetTopology(self, refs, newboundary=name) + def subset(self, topo: 'Topology', newboundary: Optional[Union[str, 'Topology']] = None, strict: bool = False) -> 'Topology': 'intersection' raise NotImplementedError + refs = [ref.empty for ref in self.references] + for ref, trans in zip(topo.references, topo.transforms): + try: + ielem = self.transforms.index(trans) + except ValueError: + assert not strict, 'elements do not form a strict subset' + else: + subref = self.references[ielem] & ref + if strict: + assert subref == ref, 'elements do not form a strict subset' + refs[ielem] = subref + if not any(refs): + return self.empty_like() + return SubsetTopology(self, refs, newboundary) + def withgroups(self, vgroups: Mapping[str, Union[str, 'Topology']] = {}, bgroups: Mapping[str, Union[str, 'Topology']] = {}, igroups: Mapping[str, Union[str, 'Topology']] = {}, pgroups: Mapping[str, Union[str, 'Topology']] = {}) -> 'Topology': - if all(isinstance(v, str) for g in (vgroups, bgroups, igroups) for v in g.values()) and not pgroups: - return _WithGroupAliases(self, types.frozendict(vgroups), types.frozendict(bgroups), types.frozendict(igroups)) - else: - raise NotImplementedError + return _WithGroupsTopology(self, vgroups, bgroups, igroups, pgroups) if vgroups or bgroups or igroups or pgroups else self def withsubdomain(self, **kwargs: 'Topology') -> 'Topology': return self.withgroups(vgroups=kwargs) @@ -672,10 +771,28 @@ def check_boundary(self, geometry: function.Array, elemwise: bool = False, ische if numpy.greater(abs(volumes - volume), tol).any(): print('divergence check failed: {} != {}'.format(volumes, volume)) - def indicator(self, subtopo: Union[str, 'Topology']) -> 'Topology': - '''Create an indicator function for a subtopology.''' + def indicator(self, subtopo): + if isinstance(subtopo, str): + subtopo = self.get_groups(*subtopo.split(',')) + missing = frozenset(subtopo.spaces) - frozenset(self.spaces) + if missing: + raise ValueError('The following spaces of the sub topology are not present in the super topology: {}'.format(','.join(missing))) + + sub_coord_system = subtopo.coord_system + for i in range(len(self.spaces) - len(subtopo.spaces) + 1): + if self.spaces[i:i + len(subtopo.spaces)] == subtopo.spaces: + break + else: + raise ValueError('Cannot create an indicator for a sub topology defined on a non-contiguous subset of spaces of the super topology.') + for space in reversed(self.spaces[:i]): + sub_coord_system = self.ref_coord_system[space] * sub_coord_system + for space in self.spaces[i + len(subtopo.spaces):]: + coord_system *= self.ref_coord_system[space] - raise NotImplementedError + trans = sub_coord_system.trans_to(self.coord_system) + values = numpy.zeros([len(self)], dtype=int) + values[numpy.unique(trans.apply_indices(numpy.arange(len(subtopo))))] = 1 + return function.get(values, 0, self.f_index) def select(self, indicator: function.Array, ischeme: str = 'bezier2', **kwargs: numpy.ndarray) -> 'Topology': # Select elements where `indicator` is strict positive at any of the @@ -745,7 +862,89 @@ def locate(self, geom, coords, *, tol=0, eps=0, maxiter=0, arguments=None, weigh located : :class:`nutils.sample.Sample` ''' - raise NotImplementedError + if ischeme is not None: + warnings.deprecation('the ischeme argument is deprecated and will be removed in future') + if scale is not None: + warnings.deprecation('the scale argument is deprecated and will be removed in future') + if max(tol, eps) <= 0: + raise ValueError('locate requires either tol or eps to be strictly positive') + coords = numpy.asarray(coords, dtype=float) + if geom.ndim == 0: + geom = geom[_] + coords = coords[..., _] + if not geom.shape == coords.shape[1:] == (self.ndims,): + raise ValueError('invalid geometry or point shape for {}D topology'.format(self.ndims)) + arguments = dict(arguments or ()) + centroids = self.sample('_centroid', None).eval(geom, **arguments) + assert len(centroids) == len(self) + ielems = parallel.shempty(len(coords), dtype=int) + points = parallel.shempty((len(coords), len(geom)), dtype=float) + _ielem = evaluable.InRange(evaluable.Argument('_locate_ielem', shape=(), dtype=int), len(self)) + _point = evaluable.Argument('_locate_point', shape=(self.ndims,)) + lower_args = function.Bound(self.ref_coord_system, (self.coord_system, self.opposite), _ielem, _point).into_lower_args() + egeom = geom.lower(lower_args) + xJ = evaluable.Tuple((egeom, evaluable.derivative(egeom, _point))).simplified + if skip_missing: + if weights is not None: + raise ValueError('weights and skip_missing are mutually exclusive') + missing = parallel.shzeros(len(coords), dtype=bool) + with parallel.ctxrange('locating', len(coords)) as ipoints: + for ipoint in ipoints: + xt = coords[ipoint] # target + dist = numpy.linalg.norm(centroids - xt, axis=1) + for ielem in numpy.argsort(dist) if maxdist is None \ + else sorted((dist < maxdist).nonzero()[0], key=dist.__getitem__): + ref = self.references[ielem] + arguments['_locate_ielem'] = ielem + arguments['_locate_point'] = p = numpy.array(ref.centroid) + ex = ep = numpy.inf + iiter = 0 + while ex > tol and ep > eps: # newton loop + if iiter > maxiter > 0: + break # maximum number of iterations reached + iiter += 1 + xp, Jp = xJ.eval(**arguments) + dx = xt - xp + ex0 = ex + ex = numpy.linalg.norm(dx) + if ex >= ex0: + break # newton is diverging + try: + dp = numpy.linalg.solve(Jp, dx) + except numpy.linalg.LinAlgError: + break # jacobian is singular + ep = numpy.linalg.norm(dp) + p += dp # NOTE: modifies arguments['_locate_point'] in place + else: + if ref.inside(p, max(eps, ep)): + ielems[ipoint] = ielem + points[ipoint] = p + break + else: + if skip_missing: + missing[ipoint] = True + else: + raise LocateError('failed to locate point: {}'.format(xt)) + if skip_missing: + ielems = ielems[~missing] + points = points[~missing] + return self._sample(ielems, points, weights) + + def _sample(self, ielems, coords, weights=None): + index = numpy.argsort(ielems, kind='stable') + sorted_ielems = ielems[index] + offsets = [0, *(sorted_ielems[:-1] != sorted_ielems[1:]).nonzero()[0]+1, len(index)] + + unique_ielems = sorted_ielems[offsets[:-1]] + coord_systems = self.coord_system.take(unique_ielems), + if len(self.coord_system) == 0 or self.opposite != self.coord_system: + coord_systems += self.opposite.take(unique_ielems), + + slices = [index[n:m] for n, m in zip(offsets[:-1], offsets[1:])] + points_ = PointsSequence.from_iter([points.CoordsPoints(coords[s]) for s in slices] if weights is None + else [points.CoordsWeightsPoints(coords[s], weights[s]) for s in slices], self.ndims) + + return Sample.new(self.ref_coord_system, coord_systems, points_, index) @property def boundary(self) -> 'Topology': @@ -872,141 +1071,123 @@ def basis_discont(self, degree: int) -> function.Basis: coeffs = [ref.get_poly_coeffs('bernstein', degree=degree) for ref in self.references] return function.DiscontBasis(coeffs, self.f_index, self.f_coords) + @property + def _disjoint_topos(self): + return self, -if os.environ.get('NUTILS_TENSORIAL', None) == 'test': # pragma: nocover - - from unittest import SkipTest - - class _TensorialTopology(Topology): - - def __and__(self, other: Any) -> Topology: - result = super().__and__(other) - if type(self) == type(other) and result is NotImplemented: - raise SkipTest('`{}` does not implement `Topology.__and__`'.format(type(self).__qualname__)) - return result - - def __rand__(self, other: Any) -> Topology: - result = super().__and__(other) - if result is NotImplemented: - raise SkipTest('`{}` does not implement `Topology.__and__`'.format(type(self).__qualname__)) - return result - - def __sub__(self, other: Any) -> Topology: - if type(self) == type(other): - raise SkipTest('`{}` does not implement `Topology.__sub__`'.format(type(self).__qualname__)) - else: - return NotImplemented - def __rsub__(self, other: Any) -> Topology: - if isinstance(other, Topology): - raise SkipTest('`{}` does not implement `Topology.__sub__`'.format(type(self).__qualname__)) - else: - return NotImplemented +class _Empty(Topology): - @property - def space(self) -> str: - raise SkipTest('`{}` does not implement `Topology.space`'.format(type(self).__qualname__)) + def __init__(self, ref_coord_system: typing.OrderedDict[str, CoordSystem], ndims: int) -> None: + super().__init__(ref_coord_system, References.empty(ndims), CoordSystem(ndims, 0), CoordSystem(ndims, 0)) - @property - def transforms(self) -> transformseq.Transforms: - raise SkipTest('`{}` does not implement `Topology.transforms`'.format(type(self).__qualname__)) + def __invert__(self) -> Topology: + return self - @property - def opposites(self) -> transformseq.Transforms: - raise SkipTest('`{}` does not implement `Topology.opposites`'.format(type(self).__qualname__)) + @property + def connectivity(self) -> Sequence[Sequence[int]]: + return tuple() - @property - def border_transforms(self) -> transformseq.Transforms: - raise SkipTest('`{}` does not implement `Topology.border_transforms`'.format(type(self).__qualname__)) + def indicator(self, subtopo: Union[str, Topology]) -> Topology: + return function.zeros((), int) - @property - def f_index(self) -> function.Array: - raise SkipTest('`{}` does not implement `Topology.f_index`'.format(type(self).__qualname__)) + def refine_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: + return self - @property - def f_coords(self) -> function.Array: - raise SkipTest('`{}` does not implement `Topology.f_coords`'.format(type(self).__qualname__)) + def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: + return _Empty(self.ref_coord_system, self.ndims - 1) - def refined_by(self, refine: Iterable[int]) -> Topology: - raise SkipTest('`{}` does not implement `Topology.refined_by`'.format(type(self).__qualname__)) + def interfaces_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: + return _Empty(self.ref_coord_system, self.ndims - 1) - def trim(self, levelset: function.Array, maxrefine: int, ndivisions: int = 8, name: str = 'trimmed', leveltopo: Optional[Topology] = None, *, arguments: Optional[_ArgDict] = None) -> Topology: - raise SkipTest('`{}` does not implement `Topology.trim`'.format(type(self).__qualname__)) + def basis_std(self, degree: int, *args, **kwargs) -> function.Array: + return function.zeros((0,)) - def subset(self, topo: Topology, newboundary: Optional[Union[str, Topology]] = None, strict: bool = False) -> Topology: - raise SkipTest('`{}` does not implement `Topology.subset`'.format(type(self).__qualname__)) + basis_spline = basis_std - def withgroups(self, vgroups: Mapping[str, Union[str, Topology]] = {}, bgroups: Mapping[str, Union[str, Topology]] = {}, igroups: Mapping[str, Union[str, Topology]] = {}, pgroups: Mapping[str, Union[str, Topology]] = {}) -> Topology: - try: - return super().withgroups(vgroups, bgroups, igroups, pgroups) - except NotImplementedError: - raise SkipTest('`{}` does not implement `Topology.withgroups`'.format(type(self).__qualname__)) + def sample(self, ischeme: str, degree: int) -> Sample: + return Sample.empty(self.ref_coord_system, self.ndims) - def indicator(self, subtopo: Union[str, Topology]) -> Topology: - raise SkipTest('`{}` does not implement `Topology.indicator`'.format(type(self).__qualname__)) - def locate(self, geom, coords, *, tol=0, eps=0, maxiter=0, arguments=None, weights=None, maxdist=None, ischeme=None, scale=None, skip_missing=False) -> Sample: - raise SkipTest('`{}` does not implement `Topology.locate`'.format(type(self).__qualname__)) +class _WithName(Topology): -else: - _TensorialTopology = Topology + def __init__(self, topo: Topology, name: str): + self.topo = topo + self.name = name + super().__init__(topo.ref_coord_system, topo.references, topo.coord_system, topo.opposite) + def get_groups(self, *groups: str) -> Topology: + if self.name in groups: + return self.topo + else: + return self.topo.get_groups(*groups) -class _EmptyUnlowerable(function.Array): + def take_unchecked(self, __indices: numpy.ndarray) -> Topology: + return _WithName(self.topo.take_unchecked(__indices), self.name) - def lower(self, args: function.LowerArgs) -> evaluable.Array: - raise ValueError('cannot lower') + def slice_unchecked(self, __s: slice, __idim: int) -> 'Topology': + return _WithName(self.topo.slice_unchecked(__s, __idim), self.name) + def __invert__(self): + return _WithName(~self.topo, self.name) -class _Empty(_TensorialTopology): + @property + def f_index(self) -> function.Array: + return self.topo.f_index - def __init__(self, spaces: Tuple[str, ...], space_dims: Tuple[int, ...], ndims: int) -> None: - super().__init__(spaces, space_dims, References.empty(ndims)) + @property + def f_coords(self) -> function.Array: + return self.topo.f_coords - def __invert__(self) -> Topology: - return self + def basis(self, *args, **kwargs) -> function.Basis: + return self.topo.basis(*args, **kwargs) - @property - def connectivity(self) -> Sequence[Sequence[int]]: - return tuple() + def sample(self, ischeme: str, degree: int) -> Sample: + return self.topo.sample(ischeme, degree) - def indicator(self, subtopo: Union[str, Topology]) -> Topology: - return function.zeros((), int) + def refine_spaces_unchecked(self, __spaces: FrozenSet[str]) -> Topology: + return _WithName(self.topo.refine_spaces_unchecked(__spaces), self.name) - @property - def f_index(self) -> function.Array: - return _EmptyUnlowerable((), int, self.spaces, {}) + def trim(self, *args, **kwargs) -> Topology: + return _WithName(self.topo.trim(*args, **kwargs), self.name) - @property - def f_coords(self) -> function.Array: - return _EmptyUnlowerable((self.ndims,), float, self.spaces, {}) + def subset(self, *args, **kwargs) -> Topology: + return _WithName(self.topo.subset(*args, **kwargs), self.name) - def refine_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: - return self + def indicator(self, subtopo): + if isinstance(subtopo, str) and self.name in subtopo.split(','): + return function.ones(()) + else: + return self.topo.indicator(subtopo) - def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: - return _Empty(self.spaces, self.space_dims, self.ndims - 1) + def select(self, *args, **kwargs) -> Topology: + return _WithName(self.topo.select(*args, **kwargs), self.name) - def interfaces_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: - return _Empty(self.spaces, self.space_dims, self.ndims - 1) + def locate(self, *args, **kwargs): + return self.topo.locate(*args, **kwargs) - def basis_std(self, degree: int, *args, **kwargs) -> function.Array: - return function.zeros((0,)) + def boundary_spaces_unchecked(self, __spaces: FrozenSet[str]) -> Topology: + return self.topo.boundary_spaces_unchecked(__spaces) - basis_spline = basis_std + def interfaces_spaces_unchecked(self, __spaces: FrozenSet[str]) -> 'Topology': + return self.topo.interfaces_spaces_unchecked(__spaces) - def sample(self, ischeme: str, degree: int) -> Sample: - return Sample.empty(self.spaces, self.ndims) + @property + def _disjoint_topos(self): + return tuple(_WithName(part, self.name) for part in self.topo._disjoint_topos) -class _DisjointUnion(_TensorialTopology): +class _DisjointUnion(Topology): def __init__(self, topo1: Topology, topo2: Topology) -> None: - if topo1.spaces != topo2.spaces or topo1.space_dims != topo2.space_dims or topo1.ndims != topo2.ndims: - raise ValueError('The topologies must have the same spaces and dimensions.') + if topo1.ref_coord_system != topo2.ref_coord_system: + raise ValueError('The topologies must have the same (order of) spaces and reference coordinate system.') self.topo1 = topo1 self.topo2 = topo2 - super().__init__(topo1.spaces, topo1.space_dims, topo1.references + topo2.references) + references = topo1.references + topo2.references + coord_system = topo1.coord_system.concat(topo2.coord_system) + opposite = topo1.opposite.concat(topo2.opposite) + super().__init__(topo1.ref_coord_system, references, coord_system, opposite) def __invert__(self) -> Topology: return Topology.disjoint_union(~self.topo1, ~self.topo2) @@ -1014,8 +1195,8 @@ def __invert__(self) -> Topology: def __and__(self, other: Any) -> Topology: if not isinstance(other, Topology): return NotImplemented - elif self.spaces != other.spaces or self.space_dims != other.space_dims or self.ndims != other.ndims: - raise ValueError('The topologies must have the same spaces and dimensions.') + elif self.ref_coord_system != other.ref_coord_system: + raise ValueError('The topologies must have the same (order of) spaces and reference coordinate system.') else: return Topology.disjoint_union(self.topo1 & other, self.topo2 & other) @@ -1054,6 +1235,7 @@ def sample(self, ischeme: str, degree: int) -> Sample: def trim(self, levelset: function.Array, maxrefine: int, ndivisions: int = 8, name: str = 'trimmed', leveltopo: Optional[Topology] = None, *, arguments: Optional[_ArgDict] = None) -> Topology: if leveltopo is not None: + # TODO return super().trim(levelset, maxrefine, ndivisions, name, leveltopo, arguments=arguments) else: topo1 = self.topo1.trim(levelset, maxrefine, ndivisions, name, arguments=arguments) @@ -1065,15 +1247,23 @@ def select(self, indicator: function.Array, ischeme: str = 'bezier2', **kwargs: topo2 = self.topo2.select(indicator, ischeme, **kwargs) return Topology.disjoint_union(topo1, topo2) + @property + def _disjoint_topos(self): + return self.topo1._disjoint_topos + self.topo2._disjoint_topos + -class _Mul(_TensorialTopology): +class _Mul(Topology): def __init__(self, topo1: Topology, topo2: Topology) -> None: if not set(topo1.spaces).isdisjoint(topo2.spaces): raise ValueError('Cannot multiply two topologies (partially) defined on the same spaces.') self.topo1 = topo1 self.topo2 = topo2 - super().__init__(topo1.spaces + topo2.spaces, topo1.space_dims + topo2.space_dims, topo1.references * topo2.references) + ref_coord_system = OrderedDict(**topo1.ref_coord_system, **topo2.ref_coord_system) + references = topo1.references * topo2.references + coord_system = topo1.coord_system * topo2.coord_system + opposite = topo1.opposite * topo2.opposite + super().__init__(ref_coord_system, references, coord_system, opposite) def __invert__(self) -> Topology: return ~self.topo1 * ~self.topo2 @@ -1113,17 +1303,15 @@ def slice_unchecked(self, indices: slice, idim: int) -> Topology: def indicator(self, subtopo: Union[str, Topology]) -> Topology: if isinstance(subtopo, str): - groups = subtopo.split(',') - hassub1 = bool(self.topo1.get_groups(*groups)) - hassub2 = bool(self.topo2.get_groups(*groups)) - if hassub1 and hassub2: - raise NotImplementedError - elif hassub1: - return self.topo1.indicator(subtopo) - elif hassub2: - return self.topo2.indicator(subtopo) - else: - return function.zeros((), int) + subtopo = self.get_groups(*subtopo.split(',')) + missing = frozenset(subtopo.spaces) - frozenset(self.spaces) + if missing: + raise ValueError('The following spaces of the sub topology are not present in the super topology: {}'.format(','.join(missing))) + + if frozenset(subtopo.spaces) <= frozenset(self.topo1.spaces): + return self.topo1.indicator(subtopo) + elif frozenset(subtopo.spaces) <= frozenset(self.topo2.spaces): + return self.topo2.indicator(subtopo) else: return super().indicator(subtopo) @@ -1161,417 +1349,80 @@ def basis(self, name: str, degree: Union[int, Sequence[int]], **kwargs) -> funct pass elif isinstance(val, int): kwargs1[attr] = kwargs2[attr] = val - elif isinstance(val, Sequence) and all(isinstance(v, int) for v in val): - if len(val) != self.ndims: - raise ValueError('argument `{}` must have length {} but got {}'.format(attr, self.ndims, len(val))) - kwargs1[attr] = val[:self.topo1.ndims] - kwargs2[attr] = val[self.topo1.ndims:] - else: - raise ValueError('argument `{}` must be `None`, an `int` or sequence of `int`'.format(attr)) - - periodic = kwargs.pop('periodic', None) - if periodic is None: - pass - elif isinstance(periodic, Sequence) and all(isinstance(p, int) for p in periodic): - kwargs1['periodic'] = tuple(p for p in periodic if p < self.topo1.ndims) - kwargs2['periodic'] = tuple(p - self.topo1.ndims for p in periodic if p >= self.topo1.ndims) - else: - raise ValueError('argument `periodic` must be `None` or a sequence of `int`') - - for attr, typ in ('knotvalues', (int, float)), ('knotmultiplicities', int), ('removedofs', int): - val = kwargs.pop(attr, None) - if val is None: - pass - elif isinstance(val, Sequence) and all(v is None or isinstance(v, Sequence) and all(isinstance(w, typ) for w in v) for v in val): - if len(val) != self.ndims: - raise ValueError('argument `{}` must have length {} but got {}'.format(attr, self.ndims, len(val))) - kwargs1[attr] = val[:self.topo1.ndims] - kwargs2[attr] = val[self.topo1.ndims:] - else: - raise ValueError('argument `{}` must be `None`, a sequence or a sequence of sequence'.format(attr)) - - kwargs1.update(kwargs) - kwargs2.update(kwargs) - - basis1 = self.topo1.basis(name, **kwargs1) - basis2 = self.topo2.basis(name, **kwargs2) - assert basis1.ndim == basis2.ndim == 1 - return numpy.ravel(basis1[:,None] * basis2[None,:]) - - def sample(self, ischeme: str, degree: int) -> Sample: - return self.topo1.sample(ischeme, degree) * self.topo2.sample(ischeme, degree) - - -class _Take(_TensorialTopology): - - def __init__(self, parent: Topology, indices: types.arraydata) -> None: - self.parent = parent - self.indices = indices = numpy.asarray(indices) - assert indices.ndim == 1 and indices.size - assert numpy.greater(indices[1:], indices[:-1]).all() - assert 0 <= indices[0] and indices[-1] < len(self.parent) - super().__init__(parent.spaces, parent.space_dims, parent.references.take(self.indices)) - - def sample(self, ischeme: str, degree: int) -> Sample: - return self.parent.sample(ischeme, degree).take_elements(self.indices) - - -class _WithGroupAliases(_TensorialTopology): - - def __init__(self, parent: Topology, vgroups: Mapping[str, str] = {}, bgroups: Mapping[str, str] = {}, igroups: Mapping[str, str] = {}) -> None: - self.parent = parent - self.vgroups = vgroups - self.bgroups = bgroups - self.igroups = igroups - super().__init__(parent.spaces, parent.space_dims, parent.references) - - def _rewrite_groups(self, groups: Iterable[str]) -> Iterator[str]: - for group in groups: - if group in self.vgroups: - yield from self.vgroups[group].split(',') - else: - yield group - - def get_groups(self, *groups: str) -> Topology: - return self.parent.get_groups(*self._rewrite_groups(groups)) - - def take_unchecked(self, indices: numpy.ndarray) -> Topology: - # NOTE: the groups are gone after take - return self.parent.take_unchecked(indices) - - def slice_unchecked(self, indices: slice, idim: int) -> Topology: - # NOTE: the groups are gone after take - return self.parent.slice_unchecked(indices, idim) - - @property - def f_index(self) -> function.Array: - return self.parent.f_index - - @property - def f_coords(self) -> function.Array: - return self.parent.f_coords - - @property - def connectivity(self) -> Sequence[Sequence[int]]: - return self.parent.connectivity - - def basis(self, name: str, *args, **kwargs) -> function.Basis: - return self.parent.basis(name, *args, **kwargs) - - def sample(self, ischeme: str, degree: int) -> Sample: - return self.parent.sample(ischeme, degree) - - def refine_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: - return _WithGroupAliases(self.parent.refine_spaces(spaces), self.vgroups, self.bgroups, self.igroups) - - def indicator(self, subtopo: Union[str, Topology]) -> Topology: - if isinstance(subtopo, str): - return self.parent.indicator(','.join(self._rewrite_groups(subtopo.split(',')))) - else: - return super().indicator(subtopo) - - def locate(self, geom, coords, *, tol=0, eps=0, maxiter=0, arguments=None, weights=None, maxdist=None, ischeme=None, scale=None, skip_missing=False) -> Sample: - return self.parent.locate(geom, coords, tol=tol, eps=eps, maxiter=maxiter, arguments=arguments, weights=weights, maxdist=maxdist, ischeme=ischeme, scale=scale, skip_missing=skip_missing) - - def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: - return _WithGroupAliases(self.parent.boundary_spaces_unchecked(spaces), self.bgroups, types.frozendict({}), types.frozendict({})) - - def interfaces_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: - return _WithGroupAliases(self.parent.interfaces_spaces_unchecked(spaces), self.igroups, types.frozendict({}), types.frozendict({})) - - -class TransformChainsTopology(Topology): - 'base class for topologies with transform chains' - - __slots__ = 'space', 'transforms', 'opposites' - __cache__ = 'border_transforms', 'boundary', 'interfaces' - - @types.apply_annotations - def __init__(self, space: _strictspace, references: types.strict[References], transforms: transformseq.stricttransforms, opposites: transformseq.stricttransforms): - assert transforms.todims == opposites.todims - assert references.ndims == opposites.fromdims == transforms.fromdims - assert len(references) == len(transforms) == len(opposites) - self.space = space - self.transforms = transforms - self.opposites = opposites - super().__init__((space,), (transforms.todims,), references) - - def empty_like(self) -> 'TransformChainsTopology': - return EmptyTopology(self.space, self.transforms.todims, self.ndims) - - def get_groups(self, *groups): - return self.empty_like() - - def take_unchecked(self, indices: numpy.ndarray) -> 'TransformChainsTopology': - indices = types.frozenarray(indices, dtype=int) - return TransformChainsTopology(self.space, self.references.take(indices), self.transforms[indices], self.opposites[indices]) - - def __invert__(self): - return OppositeTopology(self) - - def __or__(self, other): - if not isinstance(other, TransformChainsTopology) or other.space != self.space and other.ndims != self.ndims: - return super().__or__(other) - return other if not self \ - else self if not other \ - else NotImplemented if isinstance(other, UnionTopology) \ - else UnionTopology((self, other)) - - __ror__ = lambda self, other: self.__or__(other) - - def __and__(self, other): - if not isinstance(other, TransformChainsTopology) or other.space != self.space: - return super().__and__(other) - keep_self = numpy.array(list(map(other.transforms.contains_with_tail, self.transforms)), dtype=bool) - if keep_self.all(): - return self - keep_other = numpy.array(list(map(self.transforms.contains_with_tail, other.transforms)), dtype=bool) - if keep_other.all(): - return other - ind_self = types.frozenarray(keep_self.nonzero()[0], copy=False) - ind_other = types.frozenarray([i for i, trans in enumerate(other.transforms) if keep_other[i] and not self.transforms.contains(trans)], dtype=int) - # The last condition is to avoid duplicate elements. Note that we could - # have reused the result of an earlier lookup to avoid a new (using index - # instead of contains) but we choose to trade some speed for simplicity. - references = self.references.take(ind_self).chain(other.references.take(ind_other)) - transforms = transformseq.chain([self.transforms[ind_self], other.transforms[ind_other]], self.transforms.todims, self.ndims) - opposites = transformseq.chain([self.opposites[ind_self], other.opposites[ind_other]], self.transforms.todims, self.ndims) - return TransformChainsTopology(self.space, references, transforms, opposites) - - __rand__ = lambda self, other: self.__and__(other) - - def __add__(self, other): - return self | other - - def __sub__(self, other): - assert isinstance(other, TransformChainsTopology) and other.space == self.space and other.ndims == self.ndims - return other.__rsub__(self) - - def __rsub__(self, other): - assert isinstance(other, TransformChainsTopology) and other.space == self.space and other.ndims == self.ndims - return other - other.subset(self, newboundary=getattr(self, 'boundary', None)) - - @property - def border_transforms(self): - indices = set() - for btrans in self.boundary.transforms: - try: - ielem, tail = self.transforms.index_with_tail(btrans) - except ValueError: - pass - else: - indices.add(ielem) - return self.transforms[numpy.array(sorted(indices), dtype=int)] - - @property - def _index_coords(self): - index = function.transforms_index(self.space, self.transforms) - coords = function.transforms_coords(self.space, self.transforms) - return index, coords - - @property - def f_index(self): - return self._index_coords[0] - - @property - def f_coords(self): - return self._index_coords[1] - - def sample(self, ischeme, degree): - 'Create sample.' - - points = PointsSequence.from_iter((ischeme(reference, degree) for reference in self.references), self.ndims) if callable(ischeme) \ - else self.references.getpoints(ischeme, degree) - transforms = self.transforms, - if len(self.transforms) == 0 or self.opposites != self.transforms: - transforms += self.opposites, - return Sample.new(self.space, transforms, points) - - def refined_by(self, refine): - return HierarchicalTopology(self, [numpy.arange(len(self))]).refined_by(refine) - - @property - def refined(self): - return RefinedTopology(self) - - def refine_spaces_unchecked(self, spaces: Iterable[str]) -> 'TransformChainsTopology': - # Since every `TransformChainsTopology` has exactly one space, we implement - # `refine_spaces` here for all subclasses and return `self.refined` if the - # space of this topology is in the given `spaces`. Subclasses can redefine - # the `refined` property. - if not spaces: - return self - return self.refined - - def refine(self, n): - if numpy.iterable(n): - assert len(n) == self.ndims - assert all(ni == n[0] for ni in n) - n = n[0] - return self if n <= 0 else self.refined.refine(n-1) - - def trim(self, levelset, maxrefine, ndivisions=8, name='trimmed', leveltopo=None, *, arguments=None): - if arguments is None: - arguments = {} - - refs = [] - if leveltopo is None: - ielem_arg = evaluable.Argument('_trim_index', (), dtype=int) - coordinates = self.references.getpoints('vertex', maxrefine).get_evaluable_coords(ielem_arg) - levelset = levelset.lower(function.LowerArgs.for_space(self.space, (self.transforms, self.opposites), ielem_arg, coordinates)).optimized_for_numpy - with log.iter.percentage('trimming', range(len(self)), self.references) as items: - for ielem, ref in items: - levels = levelset.eval(_trim_index=ielem, **arguments) - refs.append(ref.trim(levels, maxrefine=maxrefine, ndivisions=ndivisions)) - else: - log.info('collecting leveltopo elements') - coordinates = evaluable.Points(evaluable.NPoints(), self.ndims) - ielem = evaluable.Argument('_leveltopo_ielem', (), int) - levelset = levelset.lower(function.LowerArgs.for_space(self.space, (leveltopo.transforms, leveltopo.opposites), ielem, coordinates)).optimized_for_numpy - bins = [set() for ielem in range(len(self))] - for trans in leveltopo.transforms: - ielem, tail = self.transforms.index_with_tail(trans) - bins[ielem].add(tail) - fcache = cache.WrapperCache() - with log.iter.percentage('trimming', self.references, self.transforms, bins) as items: - for ref, trans, ctransforms in items: - levels = numpy.empty(ref.nvertices_by_level(maxrefine)) - cover = list(fcache[ref.vertex_cover](frozenset(ctransforms), maxrefine)) - # confirm cover and greedily optimize order - mask = numpy.ones(len(levels), dtype=bool) - while mask.any(): - imax = numpy.argmax([mask[indices].sum() for tail, points, indices in cover]) - tail, points, indices = cover.pop(imax) - ielem = leveltopo.transforms.index(trans + tail) - levels[indices] = levelset.eval(_leveltopo_ielem=ielem, _points=points, **arguments) - mask[indices] = False - refs.append(ref.trim(levels, maxrefine=maxrefine, ndivisions=ndivisions)) - log.debug('cache', fcache.stats) - return SubsetTopology(self, refs, newboundary=name) + elif isinstance(val, Sequence) and all(isinstance(v, int) for v in val): + if len(val) != self.ndims: + raise ValueError('argument `{}` must have length {} but got {}'.format(attr, self.ndims, len(val))) + kwargs1[attr] = val[:self.topo1.ndims] + kwargs2[attr] = val[self.topo1.ndims:] + else: + raise ValueError('argument `{}` must be `None`, an `int` or sequence of `int`'.format(attr)) - def subset(self, topo, newboundary=None, strict=False): - refs = [ref.empty for ref in self.references] - for ref, trans in zip(topo.references, topo.transforms): - try: - ielem = self.transforms.index(trans) - except ValueError: - assert not strict, 'elements do not form a strict subset' + periodic = kwargs.pop('periodic', None) + if periodic is None: + pass + elif isinstance(periodic, Sequence) and all(isinstance(p, int) for p in periodic): + kwargs1['periodic'] = tuple(p for p in periodic if p < self.topo1.ndims) + kwargs2['periodic'] = tuple(p - self.topo1.ndims for p in periodic if p >= self.topo1.ndims) + else: + raise ValueError('argument `periodic` must be `None` or a sequence of `int`') + + for attr, typ in ('knotvalues', (int, float)), ('knotmultiplicities', int), ('removedofs', int): + val = kwargs.pop(attr, None) + if val is None: + pass + elif isinstance(val, Sequence) and all(v is None or isinstance(v, Sequence) and all(isinstance(w, typ) for w in v) for v in val): + if len(val) != self.ndims: + raise ValueError('argument `{}` must have length {} but got {}'.format(attr, self.ndims, len(val))) + kwargs1[attr] = val[:self.topo1.ndims] + kwargs2[attr] = val[self.topo1.ndims:] else: - subref = self.references[ielem] & ref - if strict: - assert subref == ref, 'elements do not form a strict subset' - refs[ielem] = subref - if not any(refs): - return EmptyTopology(self.space, self.transforms.todims, self.ndims) - return SubsetTopology(self, refs, newboundary) + raise ValueError('argument `{}` must be `None`, a sequence or a sequence of sequence'.format(attr)) - def withgroups(self, vgroups={}, bgroups={}, igroups={}, pgroups={}): - return WithGroupsTopology(self, vgroups, bgroups, igroups, pgroups) if vgroups or bgroups or igroups or pgroups else self + kwargs1.update(kwargs) + kwargs2.update(kwargs) - def indicator(self, subtopo): - if isinstance(subtopo, str): - subtopo = self[subtopo] - values = numpy.zeros([len(self)], dtype=int) - values[numpy.fromiter(map(self.transforms.index, subtopo.transforms), dtype=int)] = 1 - return function.get(values, 0, self.f_index) + basis1 = self.topo1.basis(name, **kwargs1) + basis2 = self.topo2.basis(name, **kwargs2) + assert basis1.ndim == basis2.ndim == 1 + return numpy.ravel(basis1[:,None] * basis2[None,:]) - @log.withcontext - def locate(self, geom, coords, *, tol=0, eps=0, maxiter=0, arguments=None, weights=None, maxdist=None, ischeme=None, scale=None, skip_missing=False): - if ischeme is not None: - warnings.deprecation('the ischeme argument is deprecated and will be removed in future') - if scale is not None: - warnings.deprecation('the scale argument is deprecated and will be removed in future') - if max(tol, eps) <= 0: - raise ValueError('locate requires either tol or eps to be strictly positive') - coords = numpy.asarray(coords, dtype=float) - if geom.ndim == 0: - geom = geom[_] - coords = coords[..., _] - if not geom.shape == coords.shape[1:] == (self.ndims,): - raise ValueError('invalid geometry or point shape for {}D topology'.format(self.ndims)) - arguments = dict(arguments or ()) - centroids = self.sample('_centroid', None).eval(geom, **arguments) - assert len(centroids) == len(self) - ielems = parallel.shempty(len(coords), dtype=int) - points = parallel.shempty((len(coords), len(geom)), dtype=float) - _ielem = evaluable.Argument('_locate_ielem', shape=(), dtype=int) - _point = evaluable.Argument('_locate_point', shape=(self.ndims,)) - egeom = geom.lower(function.LowerArgs.for_space(self.space, (self.transforms, self.opposites), _ielem, _point)) - xJ = evaluable.Tuple((egeom, evaluable.derivative(egeom, _point))).simplified - if skip_missing: - if weights is not None: - raise ValueError('weights and skip_missing are mutually exclusive') - missing = parallel.shzeros(len(coords), dtype=bool) - with parallel.ctxrange('locating', len(coords)) as ipoints: - for ipoint in ipoints: - xt = coords[ipoint] # target - dist = numpy.linalg.norm(centroids - xt, axis=1) - for ielem in numpy.argsort(dist) if maxdist is None \ - else sorted((dist < maxdist).nonzero()[0], key=dist.__getitem__): - ref = self.references[ielem] - arguments['_locate_ielem'] = ielem - arguments['_locate_point'] = p = numpy.array(ref.centroid) - ex = ep = numpy.inf - iiter = 0 - while ex > tol and ep > eps: # newton loop - if iiter > maxiter > 0: - break # maximum number of iterations reached - iiter += 1 - xp, Jp = xJ.eval(**arguments) - dx = xt - xp - ex0 = ex - ex = numpy.linalg.norm(dx) - if ex >= ex0: - break # newton is diverging - try: - dp = numpy.linalg.solve(Jp, dx) - except numpy.linalg.LinAlgError: - break # jacobian is singular - ep = numpy.linalg.norm(dp) - p += dp # NOTE: modifies arguments['_locate_point'] in place - else: - if ref.inside(p, max(eps, ep)): - ielems[ipoint] = ielem - points[ipoint] = p - break - else: - if skip_missing: - missing[ipoint] = True - else: - raise LocateError('failed to locate point: {}'.format(xt)) - if skip_missing: - ielems = ielems[~missing] - points = points[~missing] - return self._sample(ielems, points, weights) + def sample(self, ischeme: str, degree: int) -> Sample: + return self.topo1.sample(ischeme, degree) * self.topo2.sample(ischeme, degree) - def _sample(self, ielems, coords, weights=None): - index = numpy.argsort(ielems, kind='stable') - sorted_ielems = ielems[index] - offsets = [0, *(sorted_ielems[:-1] != sorted_ielems[1:]).nonzero()[0]+1, len(index)] - unique_ielems = sorted_ielems[offsets[:-1]] - transforms = self.transforms[unique_ielems], - if len(self.transforms) == 0 or self.opposites != self.transforms: - transforms += self.opposites[unique_ielems], +class _Take(Topology): - slices = [index[n:m] for n, m in zip(offsets[:-1], offsets[1:])] - points_ = PointsSequence.from_iter([points.CoordsPoints(coords[s]) for s in slices] if weights is None - else [points.CoordsWeightsPoints(coords[s], weights[s]) for s in slices], self.ndims) + def __init__(self, parent: Topology, indices: types.arraydata) -> None: + self.parent = parent + self.indices = indices = numpy.asarray(indices) + assert indices.ndim == 1 and indices.size + assert numpy.greater(indices[1:], indices[:-1]).all() + assert 0 <= indices[0] and indices[-1] < len(self.parent) + references = parent.references.take(self.indices) + coord_system = parent.coord_system.take(self.indices) + opposite = parent.opposite.take(self.indices) + super().__init__(parent.ref_coord_system, references, coord_system, opposite) - return Sample.new(self.space, transforms, points_, index) + def sample(self, ischeme: str, degree: int) -> Sample: + return self.parent.sample(ischeme, degree).take_elements(self.indices) - def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> 'TransformChainsTopology': - return self.boundary - @property +class _ConformingTopology(Topology): + + def __init__(self, ref_coord_system: OrderedDict[str, CoordSystem], references: References, coord_system: CoordSystem, opposite: CoordSystem, connectivity): + self.connectivity = connectivity + super().__init__(ref_coord_system, references, coord_system, opposite) + @log.withcontext - def boundary(self): + def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: + if spaces != frozenset(self.spaces): + return ValueError('Cannot create the boundary for a subset of spaces.') references = [] selection = [] iglobaledgeiter = itertools.count() refs_touched = False - for ielem, (ioppelems, elemref, elemtrans) in enumerate(zip(self.connectivity, self.references, self.transforms)): - for (edgetrans, edgeref), ioppelem, iglobaledge in zip(elemref.edges, ioppelems, iglobaledgeiter): + for ielem, (ioppelems, elemref) in enumerate(zip(self.connectivity, self.references)): + for edgeref, ioppelem, iglobaledge in zip(elemref.edge_refs, ioppelems, iglobaledgeiter): if edgeref: if ioppelem == -1: references.append(edgeref) @@ -1588,15 +1439,14 @@ def boundary(self): references = References.from_iter(references, self.ndims-1) else: references = self.references.edges[selection] - transforms = self.transforms.edges(self.references)[selection] - return TransformChainsTopology(self.space, references, transforms, transforms) + coord_system = self.references.edges_coord_system(self.coord_system).take(selection) + return Topology(self.space, references, coord_system, coord_system) - def interfaces_spaces_unchecked(self, spaces: FrozenSet[str]) -> 'TransformChainsTopology': - return self.interfaces - - @property @log.withcontext - def interfaces(self): + def interfaces_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: + if spaces != frozenset(self.spaces): + return ValueError('Cannot create the boundary for a subset of spaces.') + raise NotImplementedError references = [] selection = [] oppselection = [] @@ -1681,41 +1531,34 @@ def basis_bernstein(self, degree): basis_std = basis_bernstein -stricttopology = types.strict[Topology] - - class LocateError(Exception): pass -class WithGroupsTopology(TransformChainsTopology): +class _WithGroupsTopology(Topology): 'item topology' __slots__ = 'basetopo', 'vgroups', 'bgroups', 'igroups', 'pgroups' __cache__ = 'refined', - @types.apply_annotations - def __init__(self, basetopo: stricttopology, vgroups: types.frozendict = {}, bgroups: types.frozendict = {}, igroups: types.frozendict = {}, pgroups: types.frozendict = {}): + def __init__(self, basetopo: Topology, vgroups: Optional[Dict[str, Union[str, Topology]]] = None, bgroups: Optional[Dict[str, Union[str, Topology]]] = None, igroups: Optional[Dict[str, Union[str, Topology]]] = None, pgroups: Optional[Dict[str, Union[str, Topology]]] = None): assert vgroups or bgroups or igroups or pgroups self.basetopo = basetopo - self.vgroups = vgroups - self.bgroups = bgroups - self.igroups = igroups - self.pgroups = pgroups - super().__init__(basetopo.space, basetopo.references, basetopo.transforms, basetopo.opposites) - assert all(topo is Ellipsis or isinstance(topo, str) or isinstance(topo, TransformChainsTopology) and topo.ndims == basetopo.ndims for topo in self.vgroups.values()) + self.vgroups = vgroups or {} + self.bgroups = bgroups or {} + self.igroups = igroups or {} + self.pgroups = pgroups or {} + super().__init__(basetopo.ref_coord_system, basetopo.references, basetopo.coord_system, basetopo.opposite) + assert all(topo is Ellipsis or isinstance(topo, str) or isinstance(topo, Topology) and topo.ndims == basetopo.ndims for topo in self.vgroups.values()) - def __len__(self): - return len(self.basetopo) - - def get_groups(self, *groups: str) -> TransformChainsTopology: + def get_groups(self, *groups: str) -> Topology: topos = [] basegroups = [] for group in groups: if group in self.vgroups: item = self.vgroups[group] - assert isinstance(item, (TransformChainsTopology, str)) - if isinstance(item, TransformChainsTopology): + assert isinstance(item, (Topology, str)) + if isinstance(item, Topology): topos.append(item) else: basegroups.extend(item.split(',')) @@ -1725,16 +1568,12 @@ def get_groups(self, *groups: str) -> TransformChainsTopology: topos.append(self.basetopo.get_groups(*basegroups)) return functools.reduce(operator.or_, topos, self.empty_like()) - def take_unchecked(self, __indices: numpy.ndarray) -> TransformChainsTopology: + def take_unchecked(self, __indices: numpy.ndarray) -> Topology: return self.basetopo.take_unchecked(__indices) - def slice_unchecked(self, __s: slice, __idim: int) -> TransformChainsTopology: + def slice_unchecked(self, __s: slice, __idim: int) -> Topology: return self.basetopo.slice_unchecked(__s, __idim) - @property - def border_transforms(self): - return self.basetopo.border_transforms - @property def connectivity(self): return self.basetopo.connectivity @@ -1748,7 +1587,8 @@ def interfaces(self): baseitopo = self.basetopo.interfaces igroups = self.igroups.copy() for name, topo in self.igroups.items(): - if isinstance(topo, TransformChainsTopology): + if isinstance(topo, Topology): + raise NotImplementedError # last minute orientation fix s = [] for transs in zip(topo.transforms, topo.opposites): @@ -1769,7 +1609,7 @@ def points(self): ptopos = [] pnames = [] topo = self - while isinstance(topo, WithGroupsTopology): + while isinstance(topo, _WithGroupsTopology): for pname, ptopo in topo.pgroups.items(): if pname not in pnames: pnames.append(pname) @@ -1782,404 +1622,199 @@ def basis(self, name, *args, **kwargs): @property def refined(self): - groups = [{name: topo.refined if isinstance(topo, TransformChainsTopology) else topo for name, topo in groups.items()} for groups in (self.vgroups, self.bgroups, self.igroups, self.pgroups)] + groups = [{name: topo.refined if isinstance(topo, Topology) else topo for name, topo in groups.items()} for groups in (self.vgroups, self.bgroups, self.igroups, self.pgroups)] return self.basetopo.refined.withgroups(*groups) def locate(self, geom, coords, **kwargs): return self.basetopo.locate(geom, coords, **kwargs) + def sample(self, *args, **kwargs): + return self.basetopo.sample(*args, **kwargs) -class OppositeTopology(TransformChainsTopology): + +class OppositeTopology(Topology): 'opposite topology' __slots__ = 'basetopo', def __init__(self, basetopo): self.basetopo = basetopo - super().__init__(basetopo.space, basetopo.references, basetopo.opposites, basetopo.transforms) + super().__init__(basetopo.ref_coord_system, basetopo.references, basetopo.opposite, basetopo.coord_system) - def get_groups(self, *groups: str) -> TransformChainsTopology: + def get_groups(self, *groups: str) -> Topology: return ~(self.basetopo.get_groups(*groups)) - def take_unchecked(self, __indices: numpy.ndarray) -> TransformChainsTopology: + def take_unchecked(self, __indices: numpy.ndarray) -> Topology: return ~(self.basetopo.take_unchecked(__indices)) - def slice_unchecked(self, __s: slice, __idim: int) -> TransformChainsTopology: + def slice_unchecked(self, __s: slice, __idim: int) -> Topology: return ~(self.basetopo.slice_unchecked(__s, __idim)) - def __len__(self): - return len(self.basetopo) - def __invert__(self): return self.basetopo -class EmptyTopology(TransformChainsTopology): - 'empty topology' - - __slots__ = () - - @types.apply_annotations - def __init__(self, space: _strictspace, todims: types.strictint, fromdims: types.strictint): - super().__init__(space, References.empty(fromdims), transformseq.EmptyTransforms(todims, fromdims), transformseq.EmptyTransforms(todims, fromdims)) - - def __or__(self, other): - if self.space != other.space or self.ndims != other.ndims: - return NotImplemented - return other - - def __rsub__(self, other): - return other - - -def StructuredLine(space, root: transform.stricttransformitem, i: types.strictint, j: types.strictint, periodic: bool = False, bnames: types.tuple[types.strictstr] = None): - if bnames is None: - bnames = '_structured_line_dummy_boundary_left', '_structured_line_dummy_boundary_right' - return StructuredTopology(space, root, axes=(transformseq.DimAxis(i, j, j if periodic else 0, periodic),), nrefine=0, bnames=(bnames,)) - - -class StructuredTopology(TransformChainsTopology): - 'structured topology' +class _Line(_ConformingTopology): + 'structured line topology' - __slots__ = 'root', 'axes', 'nrefine', 'shape', '_bnames', '_asaffine_geom', '_asaffine_retval' + __slots__ = '_bnames', '_periodic', '_asaffine_geom', '_asaffine_retval' __cache__ = 'connectivity', 'boundary', 'interfaces' - @types.apply_annotations - def __init__(self, space, root: transform.stricttransformitem, axes: types.tuple[types.strict[transformseq.Axis]], nrefine: types.strictint = 0, bnames: types.tuple[types.tuple[types.strictstr]] = (('left', 'right'), ('bottom', 'top'), ('front', 'back'))): + def __init__(self, ref_coord_system: OrderedDict[str, CoordSystem], coord_system: CoordSystem, opposite: CoordSystem, bnames: Tuple[str, str], periodic: bool): 'constructor' - assert all(len(bname) == 2 for bname in bnames) - - self.root = root - self.axes = axes - self.nrefine = nrefine - self.shape = tuple(axis.j - axis.i for axis in self.axes if axis.isdim) self._bnames = bnames + self._periodic = periodic + references = References.uniform(element.getsimplex(1), len(coord_system)) - references = References.uniform(util.product(element.getsimplex(1 if axis.isdim else 0) for axis in self.axes), len(self)) - transforms = transformseq.StructuredTransforms(self.root, self.axes, self.nrefine) - nbounds = len(self.axes) - len(self.shape) - if nbounds == 0: - opposites = transforms + connectivity = numpy.stack([numpy.arange(1, len(references) + 1), numpy.arange(-1, len(references) - 1)], axis=1) + if len(references) == 0: + pass + elif self._periodic: + connectivity[0, 1] = len(references) - 1 + connectivity[-1, 0] = 0 else: - axes = [axis.opposite(nbounds-1) for axis in self.axes] - opposites = transformseq.StructuredTransforms(self.root, axes, self.nrefine) + connectivity[0, 1] = -1 + connectivity[-1, 0] = -1 + connectivity = types.frozenarray(connectivity, copy=False) - super().__init__(space, references, transforms, opposites) + super().__init__(ref_coord_system, references, coord_system, opposite, connectivity) def __repr__(self): - return '{}<{}>'.format(type(self).__qualname__, 'x'.join(str(axis.j-axis.i)+('p' if axis.isperiodic else '') for axis in self.axes if axis.isdim)) - - def __len__(self): - return numpy.prod(self.shape, dtype=int) + return '{}<{}{}>'.format(type(self).__qualname__, len(self), 'p' if self._periodic else '') - def slice_unchecked(self, indices: slice, idim: int) -> TransformChainsTopology: + def slice_unchecked(self, indices: slice, idim: int) -> Topology: if indices == slice(None): return self - axes = [] - for axis in self.axes: - if axis.isdim: - if idim == 0: - axis = axis.getitem(indices) - idim -= 1 - axes.append(axis) - return StructuredTopology(self.space, self.root, axes, self.nrefine, bnames=self._bnames) + return _Line( + self.ref_coord_system, + self.coord_system.slice(indices), + self.opposite.slice(indices), + self._bnames, + False) @property def periodic(self): - dimaxes = (axis for axis in self.axes if axis.isdim) - return tuple(idim for idim, axis in enumerate(dimaxes) if axis.isdim and axis.isperiodic) - - @property - def connectivity(self): - connectivity = numpy.empty(self.shape+(self.ndims, 2), dtype=int) - connectivity[...] = -1 - ielems = numpy.arange(len(self)).reshape(self.shape) - for idim in range(self.ndims): - s = (slice(None),)*idim - s1 = s + (slice(1, None),) - s2 = s + (slice(0, -1),) - connectivity[s2+(..., idim, 0)] = ielems[s1] - connectivity[s1+(..., idim, 1)] = ielems[s2] - if idim in self.periodic: - connectivity[s+(-1, ..., idim, 0)] = ielems[s+(0,)] - connectivity[s+(0, ..., idim, 1)] = ielems[s+(-1,)] - return types.frozenarray(connectivity.reshape(len(self), self.ndims*2), copy=False) - - @property - def boundary(self): - 'boundary' + return (0,) if self._periodic else () - nbounds = len(self.axes) - self.ndims - btopos = [StructuredTopology(self.space, root=self.root, axes=self.axes[:idim] + (bndaxis,) + self.axes[idim+1:], nrefine=self.nrefine, bnames=self._bnames) - for idim, axis in enumerate(self.axes) - for bndaxis in axis.boundaries(nbounds)] - if not btopos: - return EmptyTopology(self.space, self.transforms.todims, self.ndims-1) - bnames = [bname for bnames, axis in zip(self._bnames, self.axes) if axis.isdim and not axis.isperiodic for bname in bnames] - return DisjointUnionTopology(btopos, bnames) + def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: + if self._periodic: + return Topology.empty(self.ref_coord_system, 0) + references = References.uniform(element.getsimplex(0), 1) + coord_system_left = self.coord_system.edges(Simplex.line, 0).take([1]) + coord_system_right = self.coord_system.edges(Simplex.line, 0).take([2 * len(self) - 2]) + left = _WithName(Topology(self.ref_coord_system, references, coord_system_left, coord_system_left), self._bnames[0]) + right = _WithName(Topology(self.ref_coord_system, references, coord_system_right, coord_system_right), self._bnames[1]) + return Topology.disjoint_union(left, right) @property def interfaces(self): 'interfaces' - assert self.ndims > 0, 'zero-D topology has no interfaces' - itopos = [] - nbounds = len(self.axes) - self.ndims - for idim, axis in enumerate(self.axes): - if not axis.isdim: - continue - axes = (*self.axes[:idim], axis.intaxis(nbounds, side=True), *self.axes[idim+1:]) - oppaxes = (*self.axes[:idim], axis.intaxis(nbounds, side=False), *self.axes[idim+1:]) - itransforms = transformseq.StructuredTransforms(self.root, axes, self.nrefine) - iopposites = transformseq.StructuredTransforms(self.root, oppaxes, self.nrefine) - ireferences = References.uniform(util.product(element.getsimplex(1 if a.isdim else 0) for a in axes), len(itransforms)) - itopos.append(TransformChainsTopology(self.space, ireferences, itransforms, iopposites)) - assert len(itopos) == self.ndims - return DisjointUnionTopology(itopos, names=['dir{}'.format(idim) for idim in range(self.ndims)]) - - def _basis_spline(self, degree, knotvalues=None, knotmultiplicities=None, continuity=-1, periodic=None): - 'spline with structure information' - - if periodic is None: - periodic = self.periodic - - if numeric.isint(degree): - degree = [degree]*self.ndims - - assert len(degree) == self.ndims - - if knotvalues is None or isinstance(knotvalues[0], (int, float)): - knotvalues = [knotvalues] * self.ndims - else: - assert len(knotvalues) == self.ndims - - if knotmultiplicities is None or isinstance(knotmultiplicities[0], int): - knotmultiplicities = [knotmultiplicities] * self.ndims - else: - assert len(knotmultiplicities) == self.ndims - - if not numpy.iterable(continuity): - continuity = [continuity] * self.ndims - else: - assert len(continuity) == self.ndims - - vertex_structure = numpy.array(0) - stdelems = [] - dofshape = [] - slices = [] - cache = {} - for idim in range(self.ndims): - p = degree[idim] - n = self.shape[idim] - isperiodic = idim in periodic - - c = continuity[idim] - if c < 0: - c += p - assert -1 <= c < p - - k = knotvalues[idim] - if k is None: # Defaults to uniform spacing - k = numpy.arange(n+1) - else: - k = numpy.array(k) - while len(k) < n+1: - k_ = numpy.empty(len(k)*2-1) - k_[::2] = k - k_[1::2] = (k[:-1] + k[1:]) / 2 - k = k_ - assert len(k) == n+1, 'knot values do not match the topology size' - - m = knotmultiplicities[idim] - if m is None: # Defaults to open spline without internal repetitions - m = numpy.repeat(p-c, n+1) - if not isperiodic: - m[0] = m[-1] = p+1 - else: - m = numpy.array(m) - assert min(m) > 0 and max(m) <= p+1, 'incorrect multiplicity encountered' - while len(m) < n+1: - m_ = numpy.empty(len(m)*2-1, dtype=int) - m_[::2] = m - m_[1::2] = p-c - m = m_ - assert len(m) == n+1, 'knot multiplicity do not match the topology size' - - if not isperiodic: - nd = sum(m)-p-1 - npre = p+1-m[0] # Number of knots to be appended to front - npost = p+1-m[-1] # Number of knots to be appended to rear - m[0] = m[-1] = p+1 - else: - assert m[0] == m[-1], 'Periodic spline multiplicity expected' - assert m[0] < p+1, 'Endpoint multiplicity for periodic spline should be p or smaller' - - nd = sum(m[:-1]) - npre = npost = 0 - k = numpy.concatenate([k[-p-1:-1]+k[0]-k[-1], k, k[1:1+p]-k[0]+k[-1]]) - m = numpy.concatenate([m[-p-1:-1], m, m[1:1+p]]) - - km = numpy.array([ki for ki, mi in zip(k, m) for cnt in range(mi)], dtype=float) - assert len(km) == sum(m) - assert nd > 0, 'No basis functions defined. Knot vector too short.' - - stdelems_i = [] - slices_i = [] - offsets = numpy.cumsum(m[:-1])-p - if isperiodic: - offsets = offsets[p:-p] - offset0 = offsets[0]+npre - - for offset in offsets: - start = max(offset0-offset, 0) # Zero unless prepending influence - stop = p+1-max(offset-offsets[-1]+npost, 0) # Zero unless appending influence - slices_i.append(slice(offset-offset0+start, offset-offset0+stop)) - lknots = km[offset:offset+2*p] - km[offset] # Copy operation required - if p: # Normalize for optimized caching - lknots /= lknots[-1] - key = (tuple(numeric.round(lknots*numpy.iinfo(numpy.int32).max)), p) - try: - coeffs = cache[key] - except KeyError: - coeffs = cache[key] = self._localsplinebasis(lknots) - stdelems_i.append(coeffs[start:stop]) - stdelems.append(stdelems_i) - - numbers = numpy.arange(nd) - if isperiodic: - numbers = numpy.concatenate([numbers, numbers[:p]]) - vertex_structure = vertex_structure[..., _]*nd+numbers - dofshape.append(nd) - slices.append(slices_i) - - # Cache effectivity - log.debug('Local knot vector cache effectivity: {}'.format(100*(1.-len(cache)/float(sum(self.shape))))) - - # deduplicate stdelems and compute tensorial products `unique` with indices `index` - # such that unique[index[i,j]] == poly_outer_product(stdelems[0][i], stdelems[1][j]) - index = numpy.array(0) - for stdelems_i in stdelems: - unique_i, index_i = util.unique(stdelems_i, key=types.arraydata) - unique = unique_i if not index.ndim \ - else [numeric.poly_outer_product(a, b) for a in unique for b in unique_i] - index = index[..., _] * len(unique_i) + index_i - - coeffs = [unique[i] for i in index.flat] - dofmap = [types.frozenarray(vertex_structure[S].ravel(), copy=False) for S in itertools.product(*slices)] - return coeffs, dofmap, dofshape + raise NotImplementedError def basis_spline(self, degree, removedofs=None, knotvalues=None, knotmultiplicities=None, continuity=-1, periodic=None): 'spline basis' - if removedofs is None or isinstance(removedofs[0], int): - removedofs = [removedofs] * self.ndims - else: - assert len(removedofs) == self.ndims + if removedofs is not None and not isinstance(removedofs[0], int): + removedofs, = removedofs if periodic is None: - periodic = self.periodic - - if numeric.isint(degree): - degree = [degree]*self.ndims + periodic = self._periodic + else: + periodic = 0 in periodic - assert len(degree) == self.ndims + if not numeric.isint(degree): + degree, = degree - if knotvalues is None or isinstance(knotvalues[0], (int, float)): - knotvalues = [knotvalues] * self.ndims - else: - assert len(knotvalues) == self.ndims + if knotvalues is not None and not isinstance(knotvalues[0], (int, float)): + knotvalues, = knotvalues - if knotmultiplicities is None or isinstance(knotmultiplicities[0], int): - knotmultiplicities = [knotmultiplicities] * self.ndims - else: - assert len(knotmultiplicities) == self.ndims + if knotmultiplicities is not None and not isinstance(knotmultiplicities[0], int): + knotmultiplicities, = knotmultiplicities - if not numpy.iterable(continuity): - continuity = [continuity] * self.ndims - else: - assert len(continuity) == self.ndims + if numpy.iterable(continuity): + continuity, = continuity start_dofs = [] stop_dofs = [] dofshape = [] coeffs = [] cache = {} - for idim in range(self.ndims): - p = degree[idim] - n = self.shape[idim] - c = continuity[idim] - if c < 0: - c += p - assert -1 <= c < p + p = degree + n = len(self) - k = knotvalues[idim] - if k is None: - k = numpy.arange(n+1) # default to uniform spacing - else: - k = numpy.array(k) - while len(k) < n+1: - k_ = numpy.empty(len(k)*2-1) - k_[::2] = k - k_[1::2] = (k[:-1] + k[1:]) / 2 - k = k_ - assert len(k) == n+1, 'knot values do not match the topology size' - - m = knotmultiplicities[idim] - if m is None: - m = numpy.repeat(p-c, n+1) # default to open spline without internal repetitions - else: - m = numpy.array(m) - assert min(m) > 0 and max(m) <= p+1, 'incorrect multiplicity encountered' - while len(m) < n+1: - m_ = numpy.empty(len(m)*2-1, dtype=int) - m_[::2] = m - m_[1::2] = p-c - m = m_ - assert len(m) == n+1, 'knot multiplicity do not match the topology size' - - if idim in periodic and not m[0] == m[n] == p+1: # if m[0] == m[n] == p+1 the spline is discontinuous at the boundary - assert m[0] == m[n], 'periodic spline multiplicity expected' - dk = k[n] - k[0] - m = m[:n] - k = k[:n] - nd = m.sum() - while m[n:].sum() < p - m[0] + 2: - k = numpy.concatenate([k, k+dk]) - m = numpy.concatenate([m, m]) - dk *= 2 - km = numpy.array([ki for ki, mi in zip(k, m) for cnt in range(mi)], dtype=float) - if p > m[0]: - km = numpy.concatenate([km[-p+m[0]:] - dk, km]) - else: - m[0] = m[-1] = p - nd = m[:n].sum()+1 - km = numpy.array([ki for ki, mi in zip(k, m) for cnt in range(mi)], dtype=float) - - offsets = numpy.cumsum(m[:n]) - m[0] - start_dofs.append(offsets) - stop_dofs.append(offsets+p+1) - dofshape.append(nd) - - coeffs_i = [] - for offset in offsets: - lknots = km[offset:offset+2*p] - key = tuple(numeric.round((lknots[1:-1]-lknots[0])/(lknots[-1]-lknots[0])*numpy.iinfo(numpy.int32).max)) if lknots.size else (), p - try: - local_coeffs = cache[key] - except KeyError: - local_coeffs = cache[key] = self._localsplinebasis(lknots) - coeffs_i.append(local_coeffs) - coeffs.append(tuple(coeffs_i)) - - transforms_shape = tuple(axis.j-axis.i for axis in self.axes if axis.isdim) - func = function.StructuredBasis(coeffs, start_dofs, stop_dofs, dofshape, transforms_shape, self.f_index, self.f_coords) - if not any(removedofs): + c = continuity + if c < 0: + c += p + assert -1 <= c < p + + k = knotvalues + if k is None: + k = numpy.arange(n+1) # default to uniform spacing + else: + k = numpy.array(k) + while len(k) < n+1: + k_ = numpy.empty(len(k)*2-1) + k_[::2] = k + k_[1::2] = (k[:-1] + k[1:]) / 2 + k = k_ + assert len(k) == n+1, 'knot values do not match the topology size' + + m = knotmultiplicities + if m is None: + m = numpy.repeat(p-c, n+1) # default to open spline without internal repetitions + else: + m = numpy.array(m) + assert min(m) > 0 and max(m) <= p+1, 'incorrect multiplicity encountered' + while len(m) < n+1: + m_ = numpy.empty(len(m)*2-1, dtype=int) + m_[::2] = m + m_[1::2] = p-c + m = m_ + assert len(m) == n+1, 'knot multiplicity do not match the topology size' + + if periodic and not m[0] == m[n] == p+1: # if m[0] == m[n] == p+1 the spline is discontinuous at the boundary + assert m[0] == m[n], 'periodic spline multiplicity expected' + dk = k[n] - k[0] + m = m[:n] + k = k[:n] + nd = m.sum() + while m[n:].sum() < p - m[0] + 2: + k = numpy.concatenate([k, k+dk]) + m = numpy.concatenate([m, m]) + dk *= 2 + km = numpy.array([ki for ki, mi in zip(k, m) for cnt in range(mi)], dtype=float) + if p > m[0]: + km = numpy.concatenate([km[-p+m[0]:] - dk, km]) + else: + m[0] = m[-1] = p + nd = m[:n].sum()+1 + km = numpy.array([ki for ki, mi in zip(k, m) for cnt in range(mi)], dtype=float) + + offsets = numpy.cumsum(m[:n]) - m[0] + start_dofs.append(offsets) + stop_dofs.append(offsets+p+1) + dofshape.append(nd) + + coeffs_i = [] + for offset in offsets: + lknots = km[offset:offset+2*p] + key = tuple(numeric.round((lknots[1:-1]-lknots[0])/(lknots[-1]-lknots[0])*numpy.iinfo(numpy.int32).max)) if lknots.size else (), p + try: + local_coeffs = cache[key] + except KeyError: + local_coeffs = cache[key] = self._localsplinebasis(lknots) + coeffs_i.append(local_coeffs) + coeffs.append(tuple(coeffs_i)) + + func = function.StructuredBasis(coeffs, start_dofs, stop_dofs, dofshape, (n,), self.f_index, self.f_coords) + if not removedofs: return func mask = numpy.ones((), dtype=bool) - for idofs, ndofs in zip(removedofs, dofshape): + for idofs, ndofs in zip([removedofs], dofshape): mask = mask[..., _].repeat(ndofs, axis=-1) if idofs: mask[..., [numeric.normdim(ndofs, idof) for idof in idofs]] = False @@ -2231,14 +1866,18 @@ def basis_legendre(self, degree: int): raise NotImplementedError('legendre is only implemented for 1D topologies') return function.LegendreBasis(degree, len(self), self.f_index, self.f_coords) - @property - def refined(self): - 'refine non-uniformly' - - axes = [axis.refined for axis in self.axes] - return StructuredTopology(self.space, self.root, axes, self.nrefine+1, bnames=self._bnames) + def refine_spaces_unchecked(self, spaces: FrozenSet[str]): + if not spaces: + return self + return _Line( + self.ref_coord_system, + self.coord_system.children(Simplex.line, 0), + self.opposite.children(Simplex.line, 0), + self._bnames, + self._periodic) def locate(self, geom, coords, *, tol=0, eps=0, weights=None, skip_missing=False, arguments=None, **kwargs): + raise NotImplementedError coords = numpy.asarray(coords, dtype=float) if geom.ndim == 0: geom = geom[_] @@ -2289,28 +1928,11 @@ def _locate(self, geom0, scale, coords, *, eps=0, weights=None, skip_missing=Fal points = points[~missing] return self._sample(ielems, points, weights) - def __str__(self): - 'string representation' - - return '{}({})'.format(self.__class__.__name__, 'x'.join(str(n) for n in self.shape)) - - -class ConnectedTopology(TransformChainsTopology): - 'unstructured topology with connectivity' - - __slots__ = 'connectivity', - @types.apply_annotations - def __init__(self, space, references: types.strict[References], transforms: transformseq.stricttransforms, opposites: transformseq.stricttransforms, connectivity: types.tuple[types.arraydata]): - assert len(connectivity) == len(references) and all(c.shape[0] == e.nedges for c, e in zip(connectivity, references)) - self.connectivity = tuple(map(numpy.asarray, connectivity)) - super().__init__(space, references, transforms, opposites) - - -class SimplexTopology(TransformChainsTopology): +class _SimplexTopology(_ConformingTopology): 'simpex topology' - __slots__ = 'simplices', 'references', 'transforms', 'opposites' + __slots__ = 'simplices' __cache__ = 'connectivity' def _renumber(simplices): @@ -2319,29 +1941,28 @@ def _renumber(simplices): keep[simplices.flat] = True return types.arraydata(simplices if keep.all() else (numpy.cumsum(keep)-1)[simplices]) - @types.apply_annotations - def __init__(self, space, simplices: _renumber, transforms: transformseq.stricttransforms, opposites: transformseq.stricttransforms): - assert simplices.shape == (len(transforms), transforms.fromdims+1) + def __init__(self, ref_coord_system: OrderedDict[str, CoordSystem], simplices, coord_system: CoordSystem, opposite: CoordSystem): + assert simplices.shape == (len(coord_system), coord_system.dim + 1) self.simplices = numpy.asarray(simplices) assert numpy.greater(self.simplices[:, 1:], self.simplices[:, :-1]).all(), 'nodes should be sorted' assert not numpy.equal(self.simplices[:, 1:], self.simplices[:, :-1]).all(), 'duplicate nodes' - references = References.uniform(element.getsimplex(transforms.fromdims), len(transforms)) - super().__init__(space, references, transforms, opposites) + references = References.uniform(element.getsimplex(coord_system.dim), len(coord_system)) - @property - def connectivity(self): - nverts = self.ndims + 1 - edge_vertices = numpy.arange(nverts).repeat(self.ndims).reshape(self.ndims, nverts)[:, ::-1].T # nverts x ndims - simplices_edges = self.simplices.take(edge_vertices, axis=1) # nelems x nverts x ndims - elems, edges = divmod(numpy.lexsort(simplices_edges.reshape(-1, self.ndims).T), nverts) + ndims = references.ndims + nverts = ndims + 1 + edge_vertices = numpy.arange(nverts).repeat(ndims).reshape(ndims, nverts)[:, ::-1].T # nverts x ndims + simplices_edges = numpy.take(simplices, edge_vertices, axis=1) # nelems x nverts x ndims + elems, edges = divmod(numpy.lexsort(simplices_edges.reshape(-1, ndims).T), nverts) sorted_simplices_edges = simplices_edges[elems, edges] # (nelems x nverts) x ndims; matching edges are now adjacent i, = numpy.equal(sorted_simplices_edges[1:], sorted_simplices_edges[:-1]).all(axis=1).nonzero() j = i + 1 assert numpy.greater(i[1:], j[:-1]).all(), 'single edge is shared by three or more simplices' - connectivity = numpy.full((len(self.simplices), self.ndims+1), fill_value=-1, dtype=int) + connectivity = numpy.full((len(self.simplices), ndims+1), fill_value=-1, dtype=int) connectivity[elems[i], edges[i]] = elems[j] connectivity[elems[j], edges[j]] = elems[i] - return types.frozenarray(connectivity, copy=False) + connectivity = types.frozenarray(connectivity, copy=False) + + super().__init__(ref_coord_system, references, coord_system, opposite, connectivity) def basis_std(self, degree): if degree == 1: @@ -2365,13 +1986,12 @@ def basis_bubble(self): return function.PlainBasis([coeffs] * len(self), nmap, ndofs, self.f_index, self.f_coords) -class UnionTopology(TransformChainsTopology): +class UnionTopology(Topology): 'grouped topology' __slots__ = '_topos', '_names', 'references', 'transforms', 'opposites' - @types.apply_annotations - def __init__(self, topos: types.tuple[stricttopology], names: types.tuple[types.strictstr] = ()): + def __init__(self, topos: Tuple[Topology, ...], names: Tuple[str, ...] = ()): self._topos = topos self._names = tuple(names)[:len(self._topos)] assert len(set(self._names)) == len(self._names), 'duplicate name' @@ -2414,12 +2034,12 @@ def __init__(self, topos: types.tuple[stricttopology], names: types.tuple[types. transformseq.chain((topo.transforms[selection] for topo, selection in zip(topos, selections)), topos[0].transforms.todims, ndims), transformseq.chain((topo.opposites[selection] for topo, selection in zip(topos, selections)), topos[0].transforms.todims, ndims)) - def get_groups(self, *groups: str) -> TransformChainsTopology: + def get_groups(self, *groups: str) -> Topology: topos = (topo if name in groups else topo.get_groups(*groups) for topo, name in itertools.zip_longest(self._topos, self._names)) return functools.reduce(operator.or_, filter(None, topos), self.empty_like()) def __or__(self, other): - if not isinstance(other, TransformChainsTopology): + if not isinstance(other, Topology): return super().__or__(other) if not isinstance(other, UnionTopology): return UnionTopology(self._topos + (other,), self._names) @@ -2430,13 +2050,12 @@ def refined(self): return UnionTopology([topo.refined for topo in self._topos], self._names) -class DisjointUnionTopology(TransformChainsTopology): +class DisjointUnionTopology(Topology): 'grouped topology' __slots__ = '_topos', '_names' - @types.apply_annotations - def __init__(self, topos: types.tuple[stricttopology], names: types.tuple[types.strictstr] = ()): + def __init__(self, topos: Tuple[Topology, ...], names: Tuple[str, ...] = ()): self._topos = topos self._names = tuple(names)[:len(self._topos)] assert len(set(self._names)) == len(self._names), 'duplicate name' @@ -2450,7 +2069,7 @@ def __init__(self, topos: types.tuple[stricttopology], names: types.tuple[types. transformseq.chain((topo.transforms for topo in self._topos), topos[0].transforms.todims, ndims), transformseq.chain((topo.opposites for topo in self._topos), topos[0].transforms.todims, ndims)) - def get_groups(self, *groups: str) -> TransformChainsTopology: + def get_groups(self, *groups: str) -> Topology: topos = (topo if name in groups else topo.get_groups(*groups) for topo, name in itertools.zip_longest(self._topos, self._names)) topos = tuple(filter(None, topos)) if len(topos) == 0: @@ -2465,16 +2084,15 @@ def refined(self): return DisjointUnionTopology([topo.refined for topo in self._topos], self._names) -class SubsetTopology(TransformChainsTopology): +class SubsetTopology(Topology): 'trimmed' __slots__ = 'refs', 'basetopo', 'newboundary', '_indices' __cache__ = 'connectivity', 'boundary', 'interfaces', 'refined' - @types.apply_annotations - def __init__(self, basetopo: stricttopology, refs: types.tuple[element.strictreference], newboundary=None): + def __init__(self, basetopo: Topology, refs: Tuple[Reference, ...], newboundary=None): if newboundary is not None: - assert isinstance(newboundary, str) or isinstance(newboundary, TransformChainsTopology) and newboundary.ndims == basetopo.ndims-1 + assert isinstance(newboundary, str) or isinstance(newboundary, Topology) and newboundary.ndims == basetopo.ndims-1 assert len(refs) == len(basetopo) self.refs = refs self.basetopo = basetopo @@ -2486,13 +2104,13 @@ def __init__(self, basetopo: stricttopology, refs: types.tuple[element.strictref opposites = self.basetopo.opposites[self._indices] super().__init__(basetopo.space, references, transforms, opposites) - def get_groups(self, *groups: str) -> TransformChainsTopology: + def get_groups(self, *groups: str) -> Topology: return self.basetopo.get_groups(*groups).subset(self, strict=False) def __rsub__(self, other): if self.basetopo == other: refs = [baseref - ref for baseref, ref in zip(self.basetopo.references, self.refs)] - return SubsetTopology(self.basetopo, refs, ~self.newboundary if isinstance(self.newboundary, TransformChainsTopology) else self.newboundary) + return SubsetTopology(self.basetopo, refs, ~self.newboundary if isinstance(self.newboundary, Topology) else self.newboundary) return super().__rsub__(other) def __or__(self, other): @@ -2515,7 +2133,7 @@ def refined(self): indices = types.frozenarray(numpy.array([i for i, ref in enumerate(child_refs) if ref], dtype=int), copy=False) refined_transforms = self.transforms.refined(self.references)[indices] self_refined = TransformChainsTopology(self.space, child_refs[indices], refined_transforms, refined_transforms) - return self.basetopo.refined.subset(self_refined, self.newboundary.refined if isinstance(self.newboundary, TransformChainsTopology) else self.newboundary, strict=True) + return self.basetopo.refined.subset(self_refined, self.newboundary.refined if isinstance(self.newboundary, Topology) else self.newboundary, strict=True) @property def boundary(self): @@ -2558,7 +2176,7 @@ def boundary(self): trimmedtransforms.append(elemtrans+(edgetrans,)) trimmedopposites.append(elemtrans+(edgetrans.flipped,)) origboundary = SubsetTopology(baseboundary, brefs) - if isinstance(self.newboundary, TransformChainsTopology): + if isinstance(self.newboundary, Topology): trimmedbrefs = [ref.empty for ref in self.newboundary.references] for ref, trans in zip(trimmedreferences, trimmedtransforms): trimmedbrefs[self.newboundary.transforms.index(trans)] = ref @@ -2599,14 +2217,13 @@ def locate(self, geom, coords, *, eps=0, **kwargs): return sample -class RefinedTopology(TransformChainsTopology): +class RefinedTopology(Topology): 'refinement' __slots__ = 'basetopo', __cache__ = 'boundary', 'connectivity' - @types.apply_annotations - def __init__(self, basetopo: stricttopology): + def __init__(self, basetopo: Topology): self.basetopo = basetopo super().__init__( self.basetopo.space, @@ -2614,7 +2231,7 @@ def __init__(self, basetopo: stricttopology): self.basetopo.transforms.refined(self.basetopo.references), self.basetopo.opposites.refined(self.basetopo.references)) - def get_groups(self, *groups: str) -> TransformChainsTopology: + def get_groups(self, *groups: str) -> Topology: return self.basetopo.get_groups(*groups).refined @property @@ -2638,14 +2255,13 @@ def connectivity(self): return tuple(types.frozenarray(c, copy=False) for c in connectivity) -class HierarchicalTopology(TransformChainsTopology): +class HierarchicalTopology(Topology): 'collection of nested topology elments' __slots__ = 'basetopo', 'levels', '_indices_per_level', '_offsets' __cache__ = 'refined', 'boundary', 'interfaces' - @types.apply_annotations - def __init__(self, basetopo: stricttopology, indices_per_level: types.tuple[types.arraydata]): + def __init__(self, basetopo: Topology, indices_per_level: types.tuple[types.arraydata]): 'constructor' assert all(ind.dtype == int for ind in indices_per_level) @@ -2960,15 +2576,14 @@ class Patch(types.Singleton): __slots__ = 'topo', 'verts', 'boundaries' - @types.apply_annotations - def __init__(self, topo: stricttopology, verts: types.arraydata, boundaries: types.tuple[types.strict[PatchBoundary]]): + def __init__(self, topo: Topology, verts: types.arraydata, boundaries: types.tuple[types.strict[PatchBoundary]]): super().__init__() self.topo = topo self.verts = numpy.asarray(verts) self.boundaries = boundaries -class MultipatchTopology(TransformChainsTopology): +class MultipatchTopology(Topology): 'multipatch topology' __slots__ = 'patches', @@ -3042,7 +2657,7 @@ def _patchinterfaces(self): if len(data) > 1 }) - def get_groups(self, *groups: str) -> TransformChainsTopology: + def get_groups(self, *groups: str) -> Topology: topos = (patch.topo if 'patch{}'.format(i) in groups else patch.topo.get_groups(*groups) for i, patch in enumerate(self.patches)) topos = tuple(filter(None, topos)) if len(topos) == 0: @@ -3178,7 +2793,7 @@ def boundary(self): subtopos.append(patch.topo.boundary[name]) subnames.append('patch{}-{}'.format(i, name)) if len(subtopos) == 0: - return EmptyTopology(self.space, self.transforms.todims, self.ndims-1) + return Topology.empty(self.ref_coord_system, self.ndims-1) else: return DisjointUnionTopology(subtopos, subnames) @@ -3191,7 +2806,7 @@ def interfaces(self): patch via ``'intrapatch'``. ''' - intrapatchtopo = EmptyTopology(self.space, self.transforms.todims, self.ndims-1) if not self.patches else \ + intrapatchtopo = Topology.empty(self.ref_coord_system, self.ndims-1) if not self.patches else \ DisjointUnionTopology(patch.topo.interfaces for patch in self.patches) btopos = [] diff --git a/src/finite_f64.rs b/src/finite_f64.rs index 0b81f556b..d95784b04 100644 --- a/src/finite_f64.rs +++ b/src/finite_f64.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; #[derive(Debug, Clone, Copy, PartialEq)] #[repr(transparent)] @@ -21,3 +22,9 @@ impl Ord for FiniteF64 { } } } + +impl Hash for FiniteF64 { + fn hash(&self, state: &mut H) { + self.0.to_bits().hash(state); + } +} diff --git a/src/lib.rs b/src/lib.rs index d308655b9..5b6f4ef1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,9 +8,13 @@ use map::coord_system::CoordSystem; use map::Map; use numpy::{IntoPyArray, IxDyn, PyArray, PyArrayDyn, PyReadonlyArrayDyn, PyReadonlyArray2, PyArray2}; use pyo3::exceptions::{PyIndexError, PyValueError}; +use pyo3::class::basic::CompareOp; use pyo3::prelude::*; +use pyo3::types::{PySlice, PySliceIndices}; use simplex::Simplex; use std::iter; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; impl From for PyErr { fn from(err: map::Error) -> PyErr { @@ -57,6 +61,35 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { pub fn nedges(&self) -> usize { self.0.nedges() } + pub fn __richcmp__<'py>( + &self, + py: Python<'py>, + other: &'py PyAny, + op: CompareOp, + ) -> PyObject { + if let Ok(other) = PySimplex::extract(other) { + match op { + CompareOp::Eq => (self.0 == other.0).into_py(py), + CompareOp::Ne => (self.0 != other.0).into_py(py), + CompareOp::Lt | CompareOp::Le | CompareOp::Gt | CompareOp::Ge => { + py.NotImplemented() + } + } + } else { + match op { + CompareOp::Eq => false.into_py(py), + CompareOp::Ne => true.into_py(py), + CompareOp::Lt | CompareOp::Le | CompareOp::Gt | CompareOp::Ge => { + py.NotImplemented() + } + } + } + } + pub fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.0.hash(&mut hasher); + hasher.finish() + } } impl From for PySimplex { @@ -132,6 +165,35 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { pub fn __repr__(&self) -> String { format!("{:?}", self.0) } + pub fn __richcmp__<'py>( + &self, + py: Python<'py>, + other: &'py PyAny, + op: CompareOp, + ) -> PyObject { + if let Ok(other) = PyCoordSystem::extract(other) { + match op { + CompareOp::Eq => (self.0 == other.0).into_py(py), + CompareOp::Ne => (self.0 != other.0).into_py(py), + CompareOp::Lt | CompareOp::Le | CompareOp::Gt | CompareOp::Ge => { + py.NotImplemented() + } + } + } else { + match op { + CompareOp::Eq => false.into_py(py), + CompareOp::Ne => true.into_py(py), + CompareOp::Lt | CompareOp::Le | CompareOp::Gt | CompareOp::Ge => { + py.NotImplemented() + } + } + } + } + pub fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.0.hash(&mut hasher); + hasher.finish() + } pub fn __len__(&self) -> usize { self.0.len() } @@ -145,6 +207,16 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { pub fn concat(&self, other: &PyCoordSystem) -> PyResult { Ok(Self(self.0.concat(&other.0)?)) } + pub fn slice(&self, s: &PySlice) -> PyResult { + let len: isize = self.0.len().try_into()?; + let PySliceIndices { start, stop, step, slicelength } = s.indices(len.try_into()?)?; + if start == 0 && stop == len && step == 1 { + Ok(self.clone()) + } else { + let indices: Vec = (0..slicelength).map(|i| (start + i * step) as usize).collect(); + Ok(Self(self.0.take(&indices)?)) + } + } pub fn take(&self, indices: Vec) -> PyResult { Ok(Self(self.0.take(&indices)?)) } @@ -188,6 +260,35 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { pub fn __repr__(&self) -> String { format!("{:?}", self.0) } + pub fn __richcmp__<'py>( + &self, + py: Python<'py>, + other: &'py PyAny, + op: CompareOp, + ) -> PyObject { + if let Ok(other) = PyCoordTrans::extract(other) { + match op { + CompareOp::Eq => (self.0 == other.0).into_py(py), + CompareOp::Ne => (self.0 != other.0).into_py(py), + CompareOp::Lt | CompareOp::Le | CompareOp::Gt | CompareOp::Ge => { + py.NotImplemented() + } + } + } else { + match op { + CompareOp::Eq => false.into_py(py), + CompareOp::Ne => true.into_py(py), + CompareOp::Lt | CompareOp::Le | CompareOp::Gt | CompareOp::Ge => { + py.NotImplemented() + } + } + } + } + pub fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.0.hash(&mut hasher); + hasher.finish() + } pub fn __len__(&self) -> usize { self.0.len_in() } @@ -249,6 +350,10 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { pub fn is_index_map(&self) -> bool { self.0.is_index_map() } + #[getter] + pub fn basis_is_constant(&self) -> bool { + self.0.basis_is_constant() + } } m.add_class::()?; diff --git a/src/map/coord_system.rs b/src/map/coord_system.rs index 845e0fde6..0b7d5c8b2 100644 --- a/src/map/coord_system.rs +++ b/src/map/coord_system.rs @@ -10,7 +10,7 @@ use std::iter; use std::ops::Mul; use std::sync::Arc; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct UniformCoordSystem(WithBounds>); impl UniformCoordSystem { @@ -101,6 +101,7 @@ impl Map for UniformCoordSystem { dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} + dispatch! {fn basis_is_constant(&self) -> bool} } impl AllPrimitiveDecompositions for UniformCoordSystem { @@ -121,7 +122,7 @@ impl RelativeTo for UniformCoordSystem { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct CoordSystem(UniformConcat); impl CoordSystem { @@ -266,6 +267,7 @@ impl Map for CoordSystem { dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} + dispatch! {fn basis_is_constant(&self) -> bool} } impl RelativeTo for CoordSystem { diff --git a/src/map/mod.rs b/src/map/mod.rs index 6b9eda324..7020152dd 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -121,6 +121,7 @@ pub trait Map { fn is_index_map(&self) -> bool; // TODO: return Result, add index, dimension checks fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize; + fn basis_is_constant(&self) -> bool; } pub trait AddOffset { diff --git a/src/map/ops.rs b/src/map/ops.rs index 9619fa503..dfbcd7f8c 100644 --- a/src/map/ops.rs +++ b/src/map/ops.rs @@ -3,7 +3,7 @@ use num::Integer as _; use std::ops::Deref; /// The composition of two maps. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct BinaryComposition(Outer, Inner); impl BinaryComposition { @@ -94,10 +94,14 @@ impl Map for BinaryComposition { let index = self.1.update_basis(index, basis, dim_out, dim_in, offset); self.0.update_basis(index, basis, dim_out, dim_in, offset) } + #[inline] + fn basis_is_constant(&self) -> bool { + self.0.basis_is_constant() && self.1.basis_is_constant() + } } /// The composition of an unempty sequence of maps. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct UniformComposition>(Array) where M: Map, @@ -212,10 +216,14 @@ where fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { self.iter().rev().fold(index, |index, map| map.update_basis(index, basis, dim_out, dim_in, offset)) } + #[inline] + fn basis_is_constant(&self) -> bool { + self.iter().all(|map| map.basis_is_constant()) + } } /// The concatenation of two maps. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct BinaryConcat(M0, M1); impl BinaryConcat { @@ -319,10 +327,14 @@ impl Map for BinaryConcat { self.1.update_basis(index - self.0.len_in(), basis, dim_out, dim_in, offset) } } + #[inline] + fn basis_is_constant(&self) -> bool { + self.0.basis_is_constant() && self.1.basis_is_constant() + } } /// The concatenation of an unempty sequence of maps. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct UniformConcat> where M: Map, @@ -460,10 +472,14 @@ where } unreachable! {} } + #[inline] + fn basis_is_constant(&self) -> bool { + self.iter().all(|map| map.basis_is_constant()) + } } /// The product of two maps. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct BinaryProduct(M0, M1); impl BinaryProduct { @@ -558,6 +574,10 @@ impl Map for BinaryProduct { fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { unimplemented!{} } + #[inline] + fn basis_is_constant(&self) -> bool { + self.0.basis_is_constant() && self.1.basis_is_constant() + } } #[derive(Debug, Clone)] @@ -573,7 +593,7 @@ impl UnapplyIndicesData for UnapplyBinaryProduct { } /// The product of a sequence of maps. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct UniformProduct>(Array) where M: Map, @@ -758,6 +778,7 @@ where dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} + dispatch! {fn basis_is_constant(&self) -> bool} } impl FromIterator for UniformProduct> { diff --git a/src/map/primitive.rs b/src/map/primitive.rs index cf7c29ea5..a6086295e 100644 --- a/src/map/primitive.rs +++ b/src/map/primitive.rs @@ -57,6 +57,7 @@ pub trait UnboundedMap { self.dim_out() == 0 } fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize; + fn basis_is_constant(&self) -> bool; } fn coords_iter_mut( @@ -76,7 +77,7 @@ fn coords_iter_mut( }) } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Identity; impl UnboundedMap for Identity { @@ -124,13 +125,17 @@ impl UnboundedMap for Identity { fn update_basis(&self, index: usize, _basis: &mut [f64], _dim_out: usize, _dim_in: &mut usize, _offset: usize) -> usize { index } + #[inline] + fn basis_is_constant(&self) -> bool { + true + } } impl AddOffset for Identity { fn add_offset(&mut self, _offset: usize) {} } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Offset(pub M, pub usize); impl UnboundedMap for Offset { @@ -184,6 +189,10 @@ impl UnboundedMap for Offset { fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { self.0.update_basis(index, basis, dim_out, dim_in, offset + self.1) } + #[inline] + fn basis_is_constant(&self) -> bool { + self.0.basis_is_constant() + } } impl AddOffset for Offset { @@ -192,7 +201,7 @@ impl AddOffset for Offset { } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Transpose(usize, usize); impl Transpose { @@ -251,13 +260,17 @@ impl UnboundedMap for Transpose { fn update_basis(&self, index: usize, _basis: &mut [f64], _dim_out: usize, _dim_in: &mut usize, _offset: usize) -> usize { self.apply_index(index) } + #[inline] + fn basis_is_constant(&self) -> bool { + true + } } impl AddOffset for Transpose { fn add_offset(&mut self, _offset: usize) {} } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Take { indices: Arc<[usize]>, nindices: usize, @@ -323,13 +336,17 @@ impl UnboundedMap for Take { fn update_basis(&self, index: usize, _basis: &mut [f64], _dim_out: usize, _dim_in: &mut usize, _offset: usize) -> usize { self.apply_index(index) } + #[inline] + fn basis_is_constant(&self) -> bool { + true + } } impl AddOffset for Take { fn add_offset(&mut self, _offset: usize) {} } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Slice { start: usize, len_in: usize, @@ -346,6 +363,10 @@ impl Slice { len_out, } } + #[inline] + fn is_identity(&self) -> bool { + self.start == 0 && self.len_in == self.len_out + } } impl UnboundedMap for Slice { @@ -388,13 +409,17 @@ impl UnboundedMap for Slice { fn update_basis(&self, index: usize, _basis: &mut [f64], _dim_out: usize, _dim_in: &mut usize, _offset: usize) -> usize { self.apply_index(index) } + #[inline] + fn basis_is_constant(&self) -> bool { + true + } } impl AddOffset for Slice { fn add_offset(&mut self, _offset: usize) {} } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Children(Simplex); impl Children { @@ -440,6 +465,13 @@ impl UnboundedMap for Children { fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { self.0.update_child_basis(index, basis, dim_out, dim_in, offset) } + #[inline] + fn basis_is_constant(&self) -> bool { + match self.0 { + Simplex::Line => true, + _ => false, + } + } } impl From for Children { @@ -448,7 +480,7 @@ impl From for Children { } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Edges(pub Simplex); impl Edges { @@ -494,6 +526,10 @@ impl UnboundedMap for Edges { fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { self.0.update_edge_basis(index, basis, dim_out, dim_in, offset) } + #[inline] + fn basis_is_constant(&self) -> bool { + false + } } impl From for Edges { @@ -502,7 +538,7 @@ impl From for Edges { } } -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UniformPoints { points: Arc<[FiniteF64]>, npoints: usize, @@ -571,7 +607,7 @@ impl UnboundedMap for UniformPoints { basis[(i + self.point_dim) * dim_out + j] = basis[i * dim_out + j]; } } - for i in (offset..offset + self.point_dim) { + for i in offset..offset + self.point_dim { for j in 0..*dim_in { basis[i * self.point_dim + j] = 0.0; } @@ -588,10 +624,14 @@ impl UnboundedMap for UniformPoints { *dim_in += self.point_dim; index / self.npoints } + #[inline] + fn basis_is_constant(&self) -> bool { + true + } } /// An enum of primitive maps. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Primitive { Transpose(Transpose), Take(Take), @@ -700,6 +740,7 @@ impl UnboundedMap for Primitive { dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} + dispatch! {fn basis_is_constant(&self) -> bool} } impl AddOffset for Primitive { @@ -826,6 +867,14 @@ where fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { self.iter().rev().fold(index, |index, map| map.update_basis(index, basis, dim_out, dim_in, offset)) } + #[inline] + fn basis_is_constant(&self) -> bool { + if self.mod_in() == 1 { + true + } else { + self.iter().all(|map| map.basis_is_constant()) + } + } } impl AddOffset for Array @@ -838,7 +887,7 @@ where } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct WithBounds { map: M, dim_in: usize, @@ -935,6 +984,10 @@ impl Map for WithBounds { fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize { self.map.update_basis(index, basis, dim_out, dim_in, offset) } + #[inline] + fn basis_is_constant(&self) -> bool { + self.map.basis_is_constant() + } } impl AddOffset for WithBounds diff --git a/src/map/relative.rs b/src/map/relative.rs index 9ea1491cf..a86241740 100644 --- a/src/map/relative.rs +++ b/src/map/relative.rs @@ -196,7 +196,7 @@ pub trait RelativeTo: Map + Sized { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub enum Relative { Identity(WithBounds), Slice(WithBounds), @@ -252,6 +252,7 @@ impl Map for Relative { dispatch! {fn is_identity(&self) -> bool} dispatch! {fn is_index_map(&self) -> bool} dispatch! {fn update_basis(&self, index: usize, basis: &mut [f64], dim_out: usize, dim_in: &mut usize, offset: usize) -> usize} + dispatch! {fn basis_is_constant(&self) -> bool} } //impl AddOffset for Relative { @@ -328,7 +329,7 @@ impl UnapplyIndicesData for IndexOutIn { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct RelativeToConcat { rels: Vec, index_map: Arc>, @@ -403,6 +404,13 @@ impl Map for RelativeToConcat { self.rels[iin / n].update_basis(iin % n, basis, dim_out, dim_in, offset); iout } + fn basis_is_constant(&self) -> bool { + if self.len_in == 1 { + true + } else { + self.rels.iter().all(|map| map.basis_is_constant()) + } + } } //impl AddOffset for RelativeToConcat { diff --git a/src/simplex.rs b/src/simplex.rs index 08d946612..859e3b8dc 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -1,5 +1,5 @@ /// Simplex. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Simplex { Line, Triangle, From 80cf0b7c923c9e1e5499575b7b72d842b58103db Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Fri, 29 Jul 2022 22:18:22 +0200 Subject: [PATCH 44/45] WIP --- examples/adaptivity.py | 13 +- nutils/debug_flags.py | 1 + nutils/element.py | 21 +- nutils/elementseq.py | 23 ++ nutils/evaluable.py | 19 +- nutils/function.py | 54 +++- nutils/mesh.py | 23 +- nutils/sample.py | 9 +- nutils/topology.py | 601 ++++++++++++++++++++++++---------------- src/lib.rs | 12 +- src/map/coord_system.rs | 31 +++ src/map/mod.rs | 2 +- src/map/ops.rs | 12 +- src/map/primitive.rs | 12 +- src/map/relative.rs | 32 ++- src/simplex.rs | 8 + 16 files changed, 582 insertions(+), 291 deletions(-) diff --git a/examples/adaptivity.py b/examples/adaptivity.py index 750c23956..a41573f43 100644 --- a/examples/adaptivity.py +++ b/examples/adaptivity.py @@ -39,12 +39,13 @@ def main(etype: str, btype: str, degree: int, nrefine: int): x, y = geom - .5 exact = (x**2 + y**2)**(1/3) * numpy.cos(numpy.arctan2(y+x, y-x) * (2/3)) - domain = domain.trim(exact-1e-15, maxrefine=0) + #domain = domain.select(exact, ischeme='gauss0') linreg = util.linear_regressor() for irefine in treelog.iter.fraction('level', range(nrefine+1)): if irefine: + basis = domain.basis(btype, degree=degree) refdom = domain.refined refbasis = refdom.basis(btype, degree=degree) ns.add_field('vref', refbasis) @@ -52,7 +53,11 @@ def main(etype: str, btype: str, degree: int, nrefine: int): res -= refdom.boundary.integral('vref ∇_k(u) n_k dS' @ ns, degree=degree*2) indicator = res.derivative('vref').eval(**args) supp = refbasis.get_support(indicator**2 > numpy.mean(indicator**2)) - domain = domain.refined_by(refdom.transforms[supp]) + if hasattr(domain, 'coord_system'): + domain = domain.refined_by(refdom.coord_system.trans_to(domain.coord_system).apply_indices(supp)) + else: + domain = domain.refined_by(refdom.transforms[supp]) + ns = Namespace() ns.x = geom @@ -61,7 +66,9 @@ def main(etype: str, btype: str, degree: int, nrefine: int): ns.uexact = exact ns.du = 'u - uexact' - sqr = domain.boundary['trimmed'].integral('u^2 dS' @ ns, degree=degree*2) + #sqr = domain.boundary['trimmed'].integral('u^2 dS' @ ns, degree=degree*2) + sqr = domain.boundary['right,bottom'].integral('u^2 dS' @ ns, degree=degree*2) + #sqr = domain.boundary.select(x - y, 'gauss0').integral('u^2 dS' @ ns, degree=degree*2) cons = solver.optimize('u,', sqr, droptol=1e-15) sqr = domain.boundary.integral('du^2 dS' @ ns, degree=7) diff --git a/nutils/debug_flags.py b/nutils/debug_flags.py index cd6f51eb9..0bd95fd14 100644 --- a/nutils/debug_flags.py +++ b/nutils/debug_flags.py @@ -7,6 +7,7 @@ sparse = _env.pop('sparse', _all or __debug__) # check sparse chunks in evaluable lower = _env.pop('lower', _all or __debug__) # check lowered shape, dtype in function evalf = _env.pop('evalf', _all) # check evaluated arrays in evaluable +hierarchical = _env.pop('hierarchical', _all) # check hierarchical topologies if _env: warnings.warn('unused debug flags: {}'.format(', '.join(_env))) diff --git a/nutils/element.py b/nutils/element.py index a4a951e55..59435cf91 100644 --- a/nutils/element.py +++ b/nutils/element.py @@ -8,7 +8,8 @@ system, and provide pointsets for purposes of integration and sampling. """ -from . import util, numeric, cache, transform, warnings, types, points +from . import util, numeric, cache, transform, warnings, types, points, _rust +from ._rust import CoordSystem import numpy import re import math @@ -353,6 +354,15 @@ def edge_transforms(self): assert self.ndims > 0 return tuple(transform.SimplexEdge(self.ndims, i) for i in range(self.ndims+1)) + def uniform_edges_coord_system(self, coord_system: CoordSystem, offset: int) -> CoordSystem: + if self.ndims == 1: + s = _rust.Simplex.line + elif self.ndims == 2: + s = _rust.Simplex.triangle + else: + raise NotImplementedError + return coord_system.edges(s, offset) + @property def child_refs(self): return tuple([self] * (2**self.ndims)) @@ -361,6 +371,15 @@ def child_refs(self): def child_transforms(self): return tuple(transform.SimplexChild(self.ndims, ichild) for ichild in range(2**self.ndims)) + def uniform_children_coord_system(self, coord_system: CoordSystem, offset: int) -> CoordSystem: + if self.ndims == 1: + s = _rust.Simplex.line + elif self.ndims == 2: + s = nutils._rust.Simplex.triangle + else: + raise NotImplementedError + return coord_system.children(s, offset) + @property def ribbons(self): return tuple(((iedge1, iedge2), (iedge2+1, iedge1)) for iedge1 in range(self.ndims+1) for iedge2 in range(iedge1, self.ndims)) diff --git a/nutils/elementseq.py b/nutils/elementseq.py index 80f96d87a..4a88cdef0 100644 --- a/nutils/elementseq.py +++ b/nutils/elementseq.py @@ -3,6 +3,7 @@ from . import types, numeric, util from .element import Reference from .pointsseq import PointsSequence +from ._rust import CoordSystem from typing import Tuple, Sequence, Iterable, Iterator, Optional, Union, overload import abc import itertools @@ -311,6 +312,9 @@ def children(self) -> 'References': return _Derived(self, 'child_refs', self.ndims) + def children_coord_system(self, coord_system: CoordSystem) -> CoordSystem: + raise NotImplementedError + @property def edges(self) -> 'References': '''Return the sequence of edge references. @@ -325,6 +329,9 @@ def edges(self) -> 'References': return _Derived(self, 'edge_refs', self.ndims-1) + def edges_coord_system(self, coord_system: CoordSystem) -> CoordSystem: + raise NotImplementedError + @property def isuniform(self) -> 'bool': '''``True`` if all reference in this sequence are equal.''' @@ -427,10 +434,16 @@ def product(self, other: References) -> References: def children(self) -> References: return References.from_iter(self.item.child_refs, self.ndims).repeat(len(self)) + def children_coord_system(self, coord_system: CoordSystem) -> CoordSystem: + return self.item.uniform_children_coord_system(coord_system, 0) + @property def edges(self) -> References: return References.from_iter(self.item.edge_refs, self.ndims-1).repeat(len(self)) + def edges_coord_system(self, coord_system: CoordSystem) -> CoordSystem: + return self.item.uniform_edges_coord_system(coord_system, 0) + @property def isuniform(self) -> bool: return True @@ -578,10 +591,20 @@ def compress(self, mask: numpy.ndarray) -> References: def children(self) -> References: return self.sequence1.children.chain(self.sequence2.children) + def children_coord_system(self, coord_system: CoordSystem) -> CoordSystem: + coord_system1 = self.sequence1.children_coord_system(coord_system.slice(slice(0, len(self.sequence1)))) + coord_system2 = self.sequence2.children_coord_system(coord_system.slice(slice(len(self.sequence1), None))) + return coord_system1.concat(coord_system2) + @property def edges(self) -> References: return self.sequence1.edges.chain(self.sequence2.edges) + def edges_coord_system(self, coord_system: CoordSystem) -> CoordSystem: + coord_system1 = self.sequence1.edges_coord_system(coord_system.slice(slice(0, len(self.sequence1)))) + coord_system2 = self.sequence2.edges_coord_system(coord_system.slice(slice(len(self.sequence1), None))) + return coord_system1.concat(coord_system2) + def getpoints(self, ischeme: str, degree: int) -> PointsSequence: return self.sequence1.getpoints(ischeme, degree).chain(self.sequence2.getpoints(ischeme, degree)) diff --git a/nutils/evaluable.py b/nutils/evaluable.py index c681f9e3c..1cbfc3ddb 100644 --- a/nutils/evaluable.py +++ b/nutils/evaluable.py @@ -1832,13 +1832,13 @@ def _inflations(self): continue parts2 = func2_inflations[axis] dofmaps = set(parts1) | set(parts2) - if (len(parts1) < len(dofmaps) and len(parts2) < len(dofmaps) # neither set is a subset of the other; total may be dense - and self.shape[axis].isconstant and all(dofmap.isconstant for dofmap in dofmaps)): - mask = numpy.zeros(int(self.shape[axis]), dtype=bool) - for dofmap in dofmaps: - mask[dofmap.eval()] = True - if mask.all(): # axis adds up to dense - continue + #if (len(parts1) < len(dofmaps) and len(parts2) < len(dofmaps) # neither set is a subset of the other; total may be dense + # and self.shape[axis].isconstant and all(dofmap.isconstant for dofmap in dofmaps)): + # mask = numpy.zeros(int(self.shape[axis]), dtype=bool) + # for dofmap in dofmaps: + # mask[dofmap.eval()] = True + # if mask.all(): # axis adds up to dense + # continue inflations.append((axis, types.frozendict((dofmap, util.sum(parts[dofmap] for parts in (parts1, parts2) if dofmap in parts)) for dofmap in dofmaps))) return tuple(inflations) @@ -3937,10 +3937,6 @@ def _simplified(self): if self._trans.is_identity: return self._index - @property - def _node_details(self): - return super()._node_details + f'\n{hash(self)}' - class TransformBasis(Array): @@ -4440,6 +4436,7 @@ def ln(x): def divmod(x, y): + raise ValueError div = FloorDivide(*_numpy_align(x, y)) mod = x - div * y return div, mod diff --git a/nutils/function.py b/nutils/function.py index 1f57aed9c..da077eb65 100644 --- a/nutils/function.py +++ b/nutils/function.py @@ -173,6 +173,9 @@ def index_coords_at(self, spaces: Tuple[str, ...], coord_system: CoordSystem) -> assert orig_dim + dim_before + dim_after == coord_system.dim bound_coord_system = util.product(bound.coord_systems[0] for bound in bounds) + #if coord_system == bound_coord_system: + # trans = None + #else: trans = bound_coord_system.trans_to(coord_system) bound_index = bounds[0].index @@ -4002,8 +4005,8 @@ def get_support(self, dof: Union[int, numpy.ndarray]) -> numpy.ndarray: def f_dofs_coeffs(self, index: evaluable.Array) -> Tuple[evaluable.Array, evaluable.Array]: indices = [] for n in reversed(self._transforms_shape[1:]): - index, ielem = evaluable.divmod(index, n) - indices.append(ielem) + indices.append(evaluable.mod(index, n)) + index = evaluable.FloorDivide(index, n) indices.append(index) indices.reverse() ranges = [evaluable.Range(evaluable.get(lengths_i, 0, index_i)) + evaluable.get(offsets_i, 0, index_i) @@ -4051,6 +4054,53 @@ def f_dofs_coeffs(self, index: evaluable.Array) -> Tuple[evaluable.Array, evalua return dofs, p_coeffs +class ProductBasis(Basis): + '''The product of two bases. + + Parameters + ---------- + basis1 : :class:`Basis` + basis2 : :class:`Basis` + ''' + + def __init__(self, basis1: Basis, basis2: Basis): + self._basis1 = basis1 + self._basis2 = basis2 + super().__init__( + basis1.ndofs * basis2.ndofs, + basis1.nelems * basis2.nelems, + basis1.index * basis2.ndofs + basis2.index, + numpy.concatenate([basis1.coords, basis2.coords], axis=0)) + + def lower(self, args: LowerArgs) -> evaluable.Array: + basis1 = self._basis1.lower(args) + basis2 = self._basis2.lower(args) + return evaluable.ravel(evaluable.insertaxis(basis1, -1, basis2.shape[-1]) * evaluable.insertaxis(basis2, -2, basis1.shape[-1]), -1) + + #@_int_or_vec_ielem + #def get_dofs(self, ielem: Union[int, numpy.ndarray]) -> numpy.ndarray: + # ielem1, ielem2 = builtins.divmod(ielem, self._basis2.nelems) + # dofs1 = self._basis1.get_dofs(ielem1) + # dofs2 = self._basis2.get_dofs(ielem2) + # return numpy.ravel(dofs1[:,None] * self._basis2.ndofs + dofs2[None,:]) + + @_int_or_vec_dof + def get_support(self, dof: Union[int, numpy.ndarray]) -> numpy.ndarray: + dof1, dof2 = builtins.divmod(dof, self._basis2.ndofs) + ielems1 = self._basis1.get_support(dof1) + ielems2 = self._basis2.get_support(dof2) + return numpy.ravel(ielems1[:,None] * self._basis2.nelems + ielems2[None,:]) + + def f_dofs_coeffs(self, index: evaluable.Array) -> Tuple[evaluable.Array, evaluable.Array]: + index1 = evaluable.FloorDivide(index, self._basis2.nelems) + index2 = evaluable.mod(index, self._basis2.nelems) + dofs1, coeffs1 = self._basis1.f_dofs_coeffs(index1) + dofs2, coeffs2 = self._basis2.f_dofs_coeffs(index2) + dofs = evaluable.ravel(evaluable.insertaxis(dofs1, 1, dofs2.shape[0]) * self._basis2.ndofs + evaluable.insertaxis(dofs2, 0, dofs1.shape[0]), 0) + coeffs = evaluable.PolyOuterProduct(coeffs1, coeffs2) + return dofs, coeffs + + def Namespace(*args, **kwargs): from .expression_v1 import Namespace return Namespace(*args, **kwargs) diff --git a/nutils/mesh.py b/nutils/mesh.py index 2f56a537f..0b6dcfb42 100644 --- a/nutils/mesh.py +++ b/nutils/mesh.py @@ -619,6 +619,7 @@ def unitsquare(nelems, etype): square = element.getsimplex(1)**2 connectivity = list(topo.connectivity) isquares = [i * nelems + j for i in range(nelems) for j in range(nelems) if i % 2 == j % 3] + nsquares = len(isquares) dofs = list(simplices) for n in sorted(isquares, reverse=True): i, j = divmod(n, nelems) @@ -626,14 +627,26 @@ def unitsquare(nelems, etype): connectivity[n*2:(n+1)*2] = numpy.concatenate(connectivity[n*2:(n+1)*2])[[3, 2, 4, 1] if i % 2 == j % 2 else [3, 2, 0, 5]], connectivity = [c-numpy.greater(c, n*2) for c in connectivity] dofs[n*2:(n+1)*2] = numpy.unique([*dofs[n*2], *dofs[n*2+1]]), - connectivity = tuple(map(types.frozenarray, connectivity)) + reorder = numpy.argsort([len(c) == 4 for c in connectivity]) + renumber = numpy.concatenate([numpy.argsort(reorder), [-1]]) + connectivity = tuple(types.frozenarray(numpy.take(renumber, connectivity[i]), copy=False) for i in reorder) + ntriangles = len(connectivity) - nsquares + assert all(len(c) == 3 for c in connectivity[:ntriangles]) + assert all(len(c) == 4 for c in connectivity[ntriangles:]) coords = coords[numpy.argsort(numpy.unique(numpy.concatenate(dofs), return_index=True)[1])] - raise NotImplementedError - # TODO: sort references and use a concatenate - references = References.from_iter(references, 2) coord_system = CoordSystem(2, len(connectivity)) ref_coord_system = OrderedDict(X=coord_system) - topo = topology._ConformingTopology(ref_coord_system, references, coord_system, coord_system, connectivity) + triangles = Topology( + ref_coord_system, + References.uniform(element.getsimplex(2), ntriangles), + coord_system.slice(slice(0, ntriangles)), + coord_system.slice(slice(0, ntriangles))) + squares = Topology( + ref_coord_system, + References.uniform(element.getsimplex(1)**2, nsquares), + coord_system.slice(slice(ntriangles, None)), + coord_system.slice(slice(ntriangles, None))) + topo = topology._WithConnectivity(Topology.disjoint_union(triangles, squares), connectivity) geom = (topo.basis('std', degree=1) * coords.T).sum(-1) x, y = topo.boundary.sample('_centroid', None).eval(geom).T diff --git a/nutils/sample.py b/nutils/sample.py index 2b146c75f..2b625d0cf 100644 --- a/nutils/sample.py +++ b/nutils/sample.py @@ -592,19 +592,22 @@ def getindex(self, __ielem: int) -> numpy.ndarray: return (index1[:, None] * self._sample2.npoints + index2[None, :]).ravel() def get_evaluable_indices(self, __ielem: evaluable.Array) -> evaluable.Array: - ielem1, ielem2 = evaluable.divmod(__ielem, self._sample2.nelems) + ielem1 = evaluable.FloorDivide(__ielem, self._sample2.nelems) + ielem2 = evaluable.mod(__ielem, self._sample2.nelems) index1 = self._sample1.get_evaluable_indices(ielem1) index2 = self._sample2.get_evaluable_indices(ielem2) return evaluable.appendaxes(index1 * self._sample2.npoints, index2.shape) + evaluable.prependaxes(index2, index1.shape) def get_evaluable_weights(self, __ielem: evaluable.Array) -> evaluable.Array: - ielem1, ielem2 = evaluable.divmod(__ielem, self._sample2.nelems) + ielem1 = evaluable.FloorDivide(__ielem, self._sample2.nelems) + ielem2 = evaluable.mod(__ielem, self._sample2.nelems) weights1 = self._sample1.get_evaluable_weights(ielem1) weights2 = self._sample2.get_evaluable_weights(ielem2) return evaluable.einsum('A,B->AB', weights1, weights2) def get_lower_args(self, __ielem: evaluable.Array) -> function.LowerArgs: - ielem1, ielem2 = evaluable.divmod(__ielem, self._sample2.nelems) + ielem1 = evaluable.FloorDivide(__ielem, self._sample2.nelems) + ielem2 = evaluable.mod(__ielem, self._sample2.nelems) return self._sample1.get_lower_args(ielem1) | self._sample2.get_lower_args(ielem2) def get_element_tri(self, ielem: int) -> numpy.ndarray: diff --git a/nutils/topology.py b/nutils/topology.py index 8c994ee33..e1ce015d3 100644 --- a/nutils/topology.py +++ b/nutils/topology.py @@ -14,7 +14,7 @@ :mod:`nutils.element` iterators. """ -from . import element, function, evaluable, util, parallel, numeric, cache, transform, warnings, matrix, types, points, sparse +from . import debug_flags, element, function, evaluable, util, parallel, numeric, cache, transform, warnings, matrix, types, points, sparse from .sample import Sample from .element import Reference from .elementseq import References @@ -41,12 +41,12 @@ _ArgDict = Mapping[str, numpy.ndarray] -class Topology: +class Topology(types.Singleton): '''topology base class Parameters ---------- - ref_coord_system : :class:`dict` of :class:`str` to :class:`~nutils._rust.CoordSystem` + ref_coord_system : :class:`tuple` of :class:`str` and :class:`~nutils._rust.CoordSystem` pairs references : :class:`nutils.elementseq.References` The references. @@ -66,12 +66,12 @@ class Topology: __slots__ = 'ref_coord_system', 'spaces', 'space_dims', 'references', 'coord_system', 'opposite', 'ndims' @staticmethod - def empty(ref_coord_system: typing.OrderedDict[str, CoordSystem], ndims: int) -> 'Topology': + def empty(ref_coord_system: Tuple[Tuple[str, CoordSystem], ...], ndims: int) -> 'Topology': '''Return an empty topology. Parameters ---------- - ref_coord_system : :class:`collections.OrderedDict` of :class:`str` to :class:`~nutils._rust.CoordSystem` + ref_coord_system : :class:`tuple` of :class:`str` and :class:`~nutils._rust.CoordSystem` pairs ndims : :class:`int` The dimension of the empty topology. @@ -142,7 +142,7 @@ def line(space: str, nelems: int, bnames: Optional[Iterable[str]] = None, period if len(bnames) != 2 or not all(isinstance(name, str) for name in bnames): raise ValueError('argument `bnames`: expected an iterable of two `str`') coord_system = CoordSystem(1, nelems) - ref_coord_system = OrderedDict({space: coord_system}) + ref_coord_system = (space, coord_system), return _Line(ref_coord_system, coord_system, coord_system, bnames, bool(periodic)) @staticmethod @@ -156,17 +156,17 @@ def simplex(space: str, simplices: numpy.array) -> 'Topology': simplices = types.arraydata(simplices if keep.all() else (numpy.cumsum(keep)-1)[simplices]) coord_system = CoordSystem(simplices.shape[1] - 1, simplices.shape[0]) - ref_coord_system = OrderedDict({space: coord_system}) + ref_coord_system = (space, coord_system), return _SimplexTopology(ref_coord_system, simplices, coord_system, coord_system) - def __init__(self, ref_coord_system: typing.OrderedDict[str, CoordSystem], references: References, coord_system: CoordSystem, opposite: CoordSystem): - if not isinstance(ref_coord_system, OrderedDict): - raise ValueError('argument `ref_coord_system`: expected an `collections.OrderedDict`') + def __init__(self, ref_coord_system: Tuple[Tuple[str, CoordSystem], ...], references: References, coord_system: CoordSystem, opposite: CoordSystem): + if not isinstance(ref_coord_system, tuple) or not all(len(item) == 2 and isinstance(item[0], str) and isinstance(item[1], CoordSystem) for item in ref_coord_system): + raise ValueError('argument `ref_coord_system`: expected a `tuple` of `str` and `CoordSystem` pairs') if not (references.ndims == coord_system.dim == opposite.dim and len(references) == len(coord_system) == len(opposite)): raise ValueError('`references`, `coord_system` and `opposite` have different dimensions') self.ref_coord_system = ref_coord_system - self.spaces = tuple(ref_coord_system) - self.space_dims = tuple(ref.dim for ref in ref_coord_system.values()) + self.spaces = tuple(space for space, _ in ref_coord_system) + self.space_dims = tuple(ref.dim for _, ref in ref_coord_system) self.references = references self.coord_system = coord_system self.opposite = opposite @@ -227,6 +227,8 @@ def take(self, __indices: Union[numpy.ndarray, Sequence[int]]) -> 'Topology': indices = numpy.unique(indices.astype(int, casting='same_kind')) if indices[0] < 0 or indices[-1] >= len(self): raise IndexError('element index out of range') + if len(indices) == len(self): + return self return self.take_unchecked(indices) def take_unchecked(self, __indices: numpy.ndarray) -> 'Topology': @@ -325,7 +327,7 @@ def __mul__(self, other: Any) -> 'Topology': if isinstance(other, Topology): left = self._disjoint_topos right = other._disjoint_topos - empty = Topology.empty(OrderedDict(**self.ref_coord_system, **other.ref_coord_system), self.ndims + other.ndims) + empty = Topology.empty(self.ref_coord_system + other.ref_coord_system, self.ndims + other.ndims) return Topology.disjoint_union(empty, *(_Mul(l, r) for l in left for r in right if len(l) and len(r))) else: return NotImplemented @@ -365,8 +367,8 @@ def refine_iter(self) -> 'Topology': @property def _index_coords(self): - index = function.transforms_index(self.coord_system, *self.ref_coord_system) - coords = function.transforms_coords(self.coord_system, *self.ref_coord_system) + index = function.transforms_index(self.coord_system, *self.spaces) + coords = function.transforms_coords(self.coord_system, *self.spaces) return index, coords @property @@ -382,9 +384,8 @@ def f_coords(self) -> function.Array: return self._index_coords[1] def basis(self, name: str, *args, **kwargs) -> function.Basis: - ''' - Create a basis. - ''' + '''Create a basis.''' + if self.ndims == 0: return function.PlainBasis([[1]], [[0]], 1, self.f_index, self.f_coords) split = name.split('-', 1) @@ -395,6 +396,76 @@ def basis(self, name: str, *args, **kwargs) -> function.Basis: f = getattr(self, 'basis_' + name) return f(*args, **kwargs) + def basis_discont(self, degree: int) -> function.Basis: + 'discontinuous shape functions' + + assert numeric.isint(degree) and degree >= 0 + if self.references.isuniform: + coeffs = [self.references[0].get_poly_coeffs('bernstein', degree=degree)]*len(self.references) + else: + coeffs = [ref.get_poly_coeffs('bernstein', degree=degree) for ref in self.references] + return function.DiscontBasis(coeffs, self.f_index, self.f_coords) + + def _basis_c0_structured(self, name, degree): + 'C^0-continuous shape functions with lagrange stucture' + + assert numeric.isint(degree) and degree >= 0 + + connectivity = self.connectivity + + if degree == 0: + raise ValueError('Cannot build a C^0-continuous basis of degree 0. Use basis \'discont\' instead.') + + coeffs = [ref.get_poly_coeffs(name, degree=degree) for ref in self.references] + offsets = numpy.cumsum([0] + [len(c) for c in coeffs]) + dofmap = numpy.repeat(-1, offsets[-1]) + for ielem, ioppelems in enumerate(connectivity): + for iedge, jelem in enumerate(ioppelems): # loop over element neighbors and merge dofs + if jelem < ielem: + continue # either there is no neighbor along iedge or situation will be inspected from the other side + jedge = util.index(connectivity[jelem], ielem) + idofs = offsets[ielem] + self.references[ielem].get_edge_dofs(degree, iedge) + jdofs = offsets[jelem] + self.references[jelem].get_edge_dofs(degree, jedge) + for idof, jdof in zip(idofs, jdofs): + while dofmap[idof] != -1: + idof = dofmap[idof] + while dofmap[jdof] != -1: + jdof = dofmap[jdof] + if idof != jdof: + dofmap[max(idof, jdof)] = min(idof, jdof) # create left-looking pointer + # assign dof numbers left-to-right + ndofs = 0 + for i, n in enumerate(dofmap): + if n == -1: + dofmap[i] = ndofs + ndofs += 1 + else: + dofmap[i] = dofmap[n] + + elem_slices = map(slice, offsets[:-1], offsets[1:]) + dofs = tuple(types.frozenarray(dofmap[s]) for s in elem_slices) + return function.PlainBasis(coeffs, dofs, ndofs, self.f_index, self.f_coords) + + def basis_lagrange(self, degree): + 'lagrange shape functions' + + return self._basis_c0_structured('lagrange', degree) + + def basis_bernstein(self, degree): + 'bernstein shape functions' + + return self._basis_c0_structured('bernstein', degree) + + basis_std = basis_bernstein + + def basis_spline(self, degree): + 'spline basis functions' + + if degree == 1: + return self.basis('std', degree) + else: + raise NotImplementedError + def sample(self, ischeme: str, degree: int) -> Sample: 'Create sample.' @@ -403,7 +474,7 @@ def sample(self, ischeme: str, degree: int) -> Sample: coord_systems = self.coord_system, if len(self.coord_system) == 0 or self.opposite != self.coord_system: coord_systems += self.opposite, - return Sample.new(self.ref_coord_system, coord_systems, points) + return Sample.new(dict(self.ref_coord_system), coord_systems, points) @util.single_or_multiple def integrate_elementwise(self, funcs: Iterable[function.Array], *, degree: int, asfunction: bool = False, ischeme: str = 'gauss', arguments: Optional[_ArgDict] = None) -> Union[List[numpy.ndarray], List[function.Array]]: @@ -531,7 +602,7 @@ def project(self, fun: function.Array, onto: function.Array, geometry: function. def refined_by(self, refine: Iterable[int]) -> 'Topology': 'create refined space by refining dofs in existing one' - return HierarchicalTopology(self, [numpy.arange(len(self))]).refined_by(refine) + return HierarchicalTopology(self, (types.arraydata(numpy.arange(len(self), dtype=int)),)).refined_by(refine) @property def refined(self) -> 'Topology': @@ -643,7 +714,10 @@ def refine_spaces_unchecked(self, __spaces: FrozenSet[str]) -> 'Topology': doing. ''' - return RefinedTopology(self) + if self.ndims == 0: + return self + else: + return RefinedTopology(self) def refine_spaces_count(self, count: Mapping[str, int]) -> 'Topology': '''Return the topology with the given spaces refined the given amount times. @@ -740,7 +814,7 @@ def subset(self, topo: 'Topology', newboundary: Optional[Union[str, 'Topology']] return SubsetTopology(self, refs, newboundary) def withgroups(self, vgroups: Mapping[str, Union[str, 'Topology']] = {}, bgroups: Mapping[str, Union[str, 'Topology']] = {}, igroups: Mapping[str, Union[str, 'Topology']] = {}, pgroups: Mapping[str, Union[str, 'Topology']] = {}) -> 'Topology': - return _WithGroupsTopology(self, vgroups, bgroups, igroups, pgroups) if vgroups or bgroups or igroups or pgroups else self + return _WithGroupsTopology(self, types.frozendict(vgroups), types.frozendict(bgroups), types.frozendict(igroups), types.frozendict(pgroups)) if vgroups or bgroups or igroups or pgroups else self def withsubdomain(self, **kwargs: 'Topology') -> 'Topology': return self.withgroups(vgroups=kwargs) @@ -784,10 +858,10 @@ def indicator(self, subtopo): break else: raise ValueError('Cannot create an indicator for a sub topology defined on a non-contiguous subset of spaces of the super topology.') - for space in reversed(self.spaces[:i]): - sub_coord_system = self.ref_coord_system[space] * sub_coord_system - for space in self.spaces[i + len(subtopo.spaces):]: - coord_system *= self.ref_coord_system[space] + for ispace in reversed(range(i)): + sub_coord_system = self.ref_coord_system[ispace][1] * sub_coord_system + for ispace in range(i + len(subtopo.spaces), len(self.spaces)): + sub_coord_system *= self.ref_coord_system[ispace][1] trans = sub_coord_system.trans_to(self.coord_system) values = numpy.zeros([len(self)], dtype=int) @@ -802,7 +876,7 @@ def select(self, indicator: function.Array, ischeme: str = 'bezier2', **kwargs: sample = self.sample(*element.parse_legacy_ischeme(ischeme)) isactive, ielem = sample.eval([indicator > 0, self.f_index], **kwargs) selected = types.frozenarray(numpy.unique(ielem[isactive])) - return self[selected] + return self.take(selected) def locate(self, geom, coords, *, tol=0, eps=0, maxiter=0, arguments=None, weights=None, maxdist=None, ischeme=None, scale=None, skip_missing=False) -> Sample: '''Create a sample based on physical coordinates. @@ -881,7 +955,7 @@ def locate(self, geom, coords, *, tol=0, eps=0, maxiter=0, arguments=None, weigh points = parallel.shempty((len(coords), len(geom)), dtype=float) _ielem = evaluable.InRange(evaluable.Argument('_locate_ielem', shape=(), dtype=int), len(self)) _point = evaluable.Argument('_locate_point', shape=(self.ndims,)) - lower_args = function.Bound(self.ref_coord_system, (self.coord_system, self.opposite), _ielem, _point).into_lower_args() + lower_args = function.Bound(dict(self.ref_coord_system), (self.coord_system, self.opposite), _ielem, _point).into_lower_args() egeom = geom.lower(lower_args) xJ = evaluable.Tuple((egeom, evaluable.derivative(egeom, _point))).simplified if skip_missing: @@ -944,7 +1018,7 @@ def _sample(self, ielems, coords, weights=None): points_ = PointsSequence.from_iter([points.CoordsPoints(coords[s]) for s in slices] if weights is None else [points.CoordsWeightsPoints(coords[s], weights[s]) for s in slices], self.ndims) - return Sample.new(self.ref_coord_system, coord_systems, points_, index) + return Sample.new(dict(self.ref_coord_system), coord_systems, points_, index) @property def boundary(self) -> 'Topology': @@ -1004,7 +1078,34 @@ def boundary_spaces_unchecked(self, __spaces: FrozenSet[str]) -> 'Topology': absolutely sure what you are doing. ''' - raise NotImplementedError + connectivity = self.connectivity + + if __spaces != frozenset(self.spaces): + return ValueError('Cannot create the boundary for a subset of spaces.') + references = [] + selection = [] + iglobaledgeiter = itertools.count() + refs_touched = False + for ielem, (ioppelems, elemref) in enumerate(zip(connectivity, self.references)): + for edgeref, ioppelem, iglobaledge in zip(elemref.edge_refs, ioppelems, iglobaledgeiter): + if edgeref: + if ioppelem == -1: + references.append(edgeref) + selection.append(iglobaledge) + else: + ioppedge = util.index(connectivity[ioppelem], ielem) + ref = edgeref - self.references[ioppelem].edge_refs[ioppedge] + if ref: + references.append(ref) + selection.append(iglobaledge) + refs_touched = True + selection = types.frozenarray(selection, dtype=int) + if refs_touched: + references = References.from_iter(references, self.ndims-1) + else: + references = self.references.edges[selection] + coord_system = self.references.edges_coord_system(self.coord_system).take(selection) + return Topology(self.space, references, coord_system, coord_system) @property def interfaces(self) -> 'Topology': @@ -1059,17 +1160,42 @@ def interfaces_spaces_unchecked(self, __spaces: FrozenSet[str]) -> 'Topology': absolutely sure what you are doing. ''' - raise NotImplementedError + connectivity = self.connectivity - def basis_discont(self, degree: int) -> function.Basis: - 'discontinuous shape functions' + if __spaces != frozenset(self.spaces): + return ValueError('Cannot create the boundary for a subset of spaces.') - assert numeric.isint(degree) and degree >= 0 + references = [] + selection = [] + oppselection = [] + iglobaledgeiter = itertools.count() + refs_touched = False + edges = self.transforms.edges(self.references) if self.references.isuniform: - coeffs = [self.references[0].get_poly_coeffs('bernstein', degree=degree)]*len(self.references) + _nedges = self.references[0].nedges + offset = lambda ielem: ielem * _nedges else: - coeffs = [ref.get_poly_coeffs('bernstein', degree=degree) for ref in self.references] - return function.DiscontBasis(coeffs, self.f_index, self.f_coords) + offset = numpy.cumsum([0]+list(ref.nedges for ref in self.references)).__getitem__ + for ielem, (ioppelems, elemref, elemtrans) in enumerate(zip(connectivity, self.references, self.transforms)): + for (edgetrans, edgeref), ioppelem, iglobaledge in zip(elemref.edges, ioppelems, iglobaledgeiter): + if edgeref and -1 < ioppelem < ielem: + ioppedge = util.index(connectivity[ioppelem], ielem) + oppedgetrans, oppedgeref = self.references[ioppelem].edges[ioppedge] + ref = oppedgeref and edgeref & oppedgeref + if ref: + references.append(ref) + selection.append(iglobaledge) + oppselection.append(offset(ioppelem)+ioppedge) + if ref != edgeref: + refs_touched = True + selection = types.frozenarray(selection, dtype=int) + oppselection = types.frozenarray(oppselection, dtype=int) + if refs_touched: + references = References.from_iter(references, self.ndims-1) + else: + references = self.references.edges[selection] + return TransformChainsTopology(self.space, references, edges[selection], edges[oppselection]) + @property def _disjoint_topos(self): @@ -1078,7 +1204,7 @@ def _disjoint_topos(self): class _Empty(Topology): - def __init__(self, ref_coord_system: typing.OrderedDict[str, CoordSystem], ndims: int) -> None: + def __init__(self, ref_coord_system: Tuple[Tuple[str, CoordSystem], ...], ndims: int) -> None: super().__init__(ref_coord_system, References.empty(ndims), CoordSystem(ndims, 0), CoordSystem(ndims, 0)) def __invert__(self) -> Topology: @@ -1259,7 +1385,7 @@ def __init__(self, topo1: Topology, topo2: Topology) -> None: raise ValueError('Cannot multiply two topologies (partially) defined on the same spaces.') self.topo1 = topo1 self.topo2 = topo2 - ref_coord_system = OrderedDict(**topo1.ref_coord_system, **topo2.ref_coord_system) + ref_coord_system = topo1.ref_coord_system + topo2.ref_coord_system references = topo1.references * topo2.references coord_system = topo1.coord_system * topo2.coord_system opposite = topo1.opposite * topo2.opposite @@ -1384,7 +1510,7 @@ def basis(self, name: str, degree: Union[int, Sequence[int]], **kwargs) -> funct basis1 = self.topo1.basis(name, **kwargs1) basis2 = self.topo2.basis(name, **kwargs2) assert basis1.ndim == basis2.ndim == 1 - return numpy.ravel(basis1[:,None] * basis2[None,:]) + return function.ProductBasis(basis1, basis2) def sample(self, ischeme: str, degree: int) -> Sample: return self.topo1.sample(ischeme, degree) * self.topo2.sample(ischeme, degree) @@ -1403,132 +1529,89 @@ def __init__(self, parent: Topology, indices: types.arraydata) -> None: opposite = parent.opposite.take(self.indices) super().__init__(parent.ref_coord_system, references, coord_system, opposite) + @property + def connectivity(self): + parent = self.parent.connectivity + renumber = numpy.full((len(parent) + 1,), -1) + renumber[self.indices] = numpy.arange(len(self.indices)) + return tuple(types.frozenarray(numpy.take(renumber, parent[i]), copy=False) for i in self.indices) + + def _take_subtopo(self, subtopo: Topology) -> Topology: + if subtopo: + trans = subtopo.coord_system.trans_to(self.parent.coord_system) + indices = trans.unapply_indices(self.indices) + subtopo = subtopo.take(indices) + return subtopo + + def get_groups(self, *groups: str) -> Topology: + return self._take_subtopo(self.parent.get_groups(*groups)) + + def take_unchecked(self, __indices: numpy.ndarray) -> Topology: + return self.topo.take_unchecked(numpy.take(self.indices, __indices)) + + def __invert__(self): + return (~self.parent).take(self.indices) + def sample(self, ischeme: str, degree: int) -> Sample: return self.parent.sample(ischeme, degree).take_elements(self.indices) + def refine_spaces_unchecked(self, __spaces: FrozenSet[str]) -> Topology: + return self._take_subtopo(self.parent.refine_spaces_unchecked(__spaces)) + + def basis(self, name, *args, **kwargs): + basis = self.parent.basis(name, *args, **kwargs) + return function.PrunedBasis(basis, self.indices, self.f_index, self.f_coords) + -class _ConformingTopology(Topology): +class _WithConnectivity(Topology): - def __init__(self, ref_coord_system: OrderedDict[str, CoordSystem], references: References, coord_system: CoordSystem, opposite: CoordSystem, connectivity): + def __init__(self, topo: Topology, connectivity): + self.topo = topo self.connectivity = connectivity - super().__init__(ref_coord_system, references, coord_system, opposite) + super().__init__(topo.ref_coord_system, topo.references, topo.coord_system, topo.opposite) - @log.withcontext - def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: - if spaces != frozenset(self.spaces): - return ValueError('Cannot create the boundary for a subset of spaces.') - references = [] - selection = [] - iglobaledgeiter = itertools.count() - refs_touched = False - for ielem, (ioppelems, elemref) in enumerate(zip(self.connectivity, self.references)): - for edgeref, ioppelem, iglobaledge in zip(elemref.edge_refs, ioppelems, iglobaledgeiter): - if edgeref: - if ioppelem == -1: - references.append(edgeref) - selection.append(iglobaledge) - else: - ioppedge = util.index(self.connectivity[ioppelem], ielem) - ref = edgeref - self.references[ioppelem].edge_refs[ioppedge] - if ref: - references.append(ref) - selection.append(iglobaledge) - refs_touched = True - selection = types.frozenarray(selection, dtype=int) - if refs_touched: - references = References.from_iter(references, self.ndims-1) - else: - references = self.references.edges[selection] - coord_system = self.references.edges_coord_system(self.coord_system).take(selection) - return Topology(self.space, references, coord_system, coord_system) + def get_groups(self, *groups: str) -> Topology: + return self.topo.get_groups(*groups) - @log.withcontext - def interfaces_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: - if spaces != frozenset(self.spaces): - return ValueError('Cannot create the boundary for a subset of spaces.') - raise NotImplementedError - references = [] - selection = [] - oppselection = [] - iglobaledgeiter = itertools.count() - refs_touched = False - edges = self.transforms.edges(self.references) - if self.references.isuniform: - _nedges = self.references[0].nedges - offset = lambda ielem: ielem * _nedges - else: - offset = numpy.cumsum([0]+list(ref.nedges for ref in self.references)).__getitem__ - for ielem, (ioppelems, elemref, elemtrans) in enumerate(zip(self.connectivity, self.references, self.transforms)): - for (edgetrans, edgeref), ioppelem, iglobaledge in zip(elemref.edges, ioppelems, iglobaledgeiter): - if edgeref and -1 < ioppelem < ielem: - ioppedge = util.index(self.connectivity[ioppelem], ielem) - oppedgetrans, oppedgeref = self.references[ioppelem].edges[ioppedge] - ref = oppedgeref and edgeref & oppedgeref - if ref: - references.append(ref) - selection.append(iglobaledge) - oppselection.append(offset(ioppelem)+ioppedge) - if ref != edgeref: - refs_touched = True - selection = types.frozenarray(selection, dtype=int) - oppselection = types.frozenarray(oppselection, dtype=int) - if refs_touched: - references = References.from_iter(references, self.ndims-1) - else: - references = self.references.edges[selection] - return TransformChainsTopology(self.space, references, edges[selection], edges[oppselection]) + def take_unchecked(self, __indices: numpy.ndarray) -> Topology: + return self.topo.take_unchecked(__indices) - def basis_spline(self, degree): - assert degree == 1 - return self.basis('std', degree) + def slice_unchecked(self, __s: slice, __idim: int) -> Topology: + return self.topo.slice_unchecked(__s, __idim) - def _basis_c0_structured(self, name, degree): - 'C^0-continuous shape functions with lagrange stucture' + def __invert__(self): + return _WithConnectivity(~self.topo, self.connectivity) - assert numeric.isint(degree) and degree >= 0 + @property + def f_index(self) -> function.Array: + return self.topo.f_index - if degree == 0: - raise ValueError('Cannot build a C^0-continuous basis of degree 0. Use basis \'discont\' instead.') + @property + def f_coords(self) -> function.Array: + return self.topo.f_coords - coeffs = [ref.get_poly_coeffs(name, degree=degree) for ref in self.references] - offsets = numpy.cumsum([0] + [len(c) for c in coeffs]) - dofmap = numpy.repeat(-1, offsets[-1]) - for ielem, ioppelems in enumerate(self.connectivity): - for iedge, jelem in enumerate(ioppelems): # loop over element neighbors and merge dofs - if jelem < ielem: - continue # either there is no neighbor along iedge or situation will be inspected from the other side - jedge = util.index(self.connectivity[jelem], ielem) - idofs = offsets[ielem] + self.references[ielem].get_edge_dofs(degree, iedge) - jdofs = offsets[jelem] + self.references[jelem].get_edge_dofs(degree, jedge) - for idof, jdof in zip(idofs, jdofs): - while dofmap[idof] != -1: - idof = dofmap[idof] - while dofmap[jdof] != -1: - jdof = dofmap[jdof] - if idof != jdof: - dofmap[max(idof, jdof)] = min(idof, jdof) # create left-looking pointer - # assign dof numbers left-to-right - ndofs = 0 - for i, n in enumerate(dofmap): - if n == -1: - dofmap[i] = ndofs - ndofs += 1 - else: - dofmap[i] = dofmap[n] + def sample(self, ischeme: str, degree: int) -> Sample: + return self.topo.sample(ischeme, degree) - elem_slices = map(slice, offsets[:-1], offsets[1:]) - dofs = tuple(types.frozenarray(dofmap[s]) for s in elem_slices) - return function.PlainBasis(coeffs, dofs, ndofs, self.f_index, self.f_coords) + def refine_spaces_unchecked(self, __spaces: FrozenSet[str]) -> Topology: + # TODO: add connectivity + return self.topo.refine_spaces_unchecked(__spaces) - def basis_lagrange(self, degree): - 'lagrange shape functions' - return self._basis_c0_structured('lagrange', degree) + def trim(self, *args, **kwargs) -> Topology: + return self.topo.trim(*args, **kwargs) - def basis_bernstein(self, degree): - 'bernstein shape functions' - return self._basis_c0_structured('bernstein', degree) + def subset(self, *args, **kwargs) -> Topology: + # TODO: add connectivity + return self.topo.subset(*args, **kwargs) - basis_std = basis_bernstein + def indicator(self, subtopo): + return self.topo.indicator(subtopo) + + def select(self, *args, **kwargs) -> Topology: + return self.topo.select(*args, **kwargs) + + def locate(self, *args, **kwargs): + return self.topo.locate(*args, **kwargs) class LocateError(Exception): @@ -1574,10 +1657,6 @@ def take_unchecked(self, __indices: numpy.ndarray) -> Topology: def slice_unchecked(self, __s: slice, __idim: int) -> Topology: return self.basetopo.slice_unchecked(__s, __idim) - @property - def connectivity(self): - return self.basetopo.connectivity - @property def boundary(self): return self.basetopo.boundary.withgroups(self.bgroups) @@ -1654,31 +1733,32 @@ def __invert__(self): return self.basetopo -class _Line(_ConformingTopology): +class _Line(Topology): 'structured line topology' __slots__ = '_bnames', '_periodic', '_asaffine_geom', '_asaffine_retval' __cache__ = 'connectivity', 'boundary', 'interfaces' - def __init__(self, ref_coord_system: OrderedDict[str, CoordSystem], coord_system: CoordSystem, opposite: CoordSystem, bnames: Tuple[str, str], periodic: bool): + def __init__(self, ref_coord_system: Tuple[Tuple[str, CoordSystem], ...], coord_system: CoordSystem, opposite: CoordSystem, bnames: Tuple[str, str], periodic: bool): 'constructor' self._bnames = bnames self._periodic = periodic references = References.uniform(element.getsimplex(1), len(coord_system)) + super().__init__(ref_coord_system, references, coord_system, opposite) - connectivity = numpy.stack([numpy.arange(1, len(references) + 1), numpy.arange(-1, len(references) - 1)], axis=1) - if len(references) == 0: + @property + def connectivity(self): + connectivity = numpy.stack([numpy.arange(1, len(self) + 1), numpy.arange(-1, len(self) - 1)], axis=1) + if len(self) == 0: pass elif self._periodic: - connectivity[0, 1] = len(references) - 1 + connectivity[0, 1] = len(self) - 1 connectivity[-1, 0] = 0 else: connectivity[0, 1] = -1 connectivity[-1, 0] = -1 - connectivity = types.frozenarray(connectivity, copy=False) - - super().__init__(ref_coord_system, references, coord_system, opposite, connectivity) + return types.frozenarray(connectivity, copy=False) def __repr__(self): return '{}<{}{}>'.format(type(self).__qualname__, len(self), 'p' if self._periodic else '') @@ -1697,9 +1777,8 @@ def slice_unchecked(self, indices: slice, idim: int) -> Topology: def periodic(self): return (0,) if self._periodic else () - def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: - if self._periodic: - return Topology.empty(self.ref_coord_system, 0) + @property + def boundary(self): references = References.uniform(element.getsimplex(0), 1) coord_system_left = self.coord_system.edges(Simplex.line, 0).take([1]) coord_system_right = self.coord_system.edges(Simplex.line, 0).take([2 * len(self) - 2]) @@ -1707,6 +1786,11 @@ def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: right = _WithName(Topology(self.ref_coord_system, references, coord_system_right, coord_system_right), self._bnames[1]) return Topology.disjoint_union(left, right) + def boundary_spaces_unchecked(self, spaces: FrozenSet[str]) -> Topology: + if self._periodic: + return Topology.empty(self.ref_coord_system, 0) + return self.boundary + @property def interfaces(self): 'interfaces' @@ -1929,7 +2013,7 @@ def _locate(self, geom0, scale, coords, *, eps=0, weights=None, skip_missing=Fal return self._sample(ielems, points, weights) -class _SimplexTopology(_ConformingTopology): +class _SimplexTopology(Topology): 'simpex topology' __slots__ = 'simplices' @@ -1941,17 +2025,20 @@ def _renumber(simplices): keep[simplices.flat] = True return types.arraydata(simplices if keep.all() else (numpy.cumsum(keep)-1)[simplices]) - def __init__(self, ref_coord_system: OrderedDict[str, CoordSystem], simplices, coord_system: CoordSystem, opposite: CoordSystem): + def __init__(self, ref_coord_system: Tuple[Tuple[str, CoordSystem], ...], simplices, coord_system: CoordSystem, opposite: CoordSystem): assert simplices.shape == (len(coord_system), coord_system.dim + 1) self.simplices = numpy.asarray(simplices) assert numpy.greater(self.simplices[:, 1:], self.simplices[:, :-1]).all(), 'nodes should be sorted' assert not numpy.equal(self.simplices[:, 1:], self.simplices[:, :-1]).all(), 'duplicate nodes' references = References.uniform(element.getsimplex(coord_system.dim), len(coord_system)) + super().__init__(ref_coord_system, references, coord_system, opposite) - ndims = references.ndims + @property + def connectivity(self): + ndims = self.references.ndims nverts = ndims + 1 edge_vertices = numpy.arange(nverts).repeat(ndims).reshape(ndims, nverts)[:, ::-1].T # nverts x ndims - simplices_edges = numpy.take(simplices, edge_vertices, axis=1) # nelems x nverts x ndims + simplices_edges = numpy.take(self.simplices, edge_vertices, axis=1) # nelems x nverts x ndims elems, edges = divmod(numpy.lexsort(simplices_edges.reshape(-1, ndims).T), nverts) sorted_simplices_edges = simplices_edges[elems, edges] # (nelems x nverts) x ndims; matching edges are now adjacent i, = numpy.equal(sorted_simplices_edges[1:], sorted_simplices_edges[:-1]).all(axis=1).nonzero() @@ -1960,9 +2047,7 @@ def __init__(self, ref_coord_system: OrderedDict[str, CoordSystem], simplices, c connectivity = numpy.full((len(self.simplices), ndims+1), fill_value=-1, dtype=int) connectivity[elems[i], edges[i]] = elems[j] connectivity[elems[j], edges[j]] = elems[i] - connectivity = types.frozenarray(connectivity, copy=False) - - super().__init__(ref_coord_system, references, coord_system, opposite, connectivity) + return types.frozenarray(connectivity, copy=False) def basis_std(self, degree): if degree == 1: @@ -2255,6 +2340,29 @@ def connectivity(self): return tuple(types.frozenarray(c, copy=False) for c in connectivity) +def _transform_poly(trans, index, coeffs): + assert coeffs.ndim == trans.from_dim + 1 + degree = coeffs.shape[1] - 1 + assert all(n == degree+1 for n in coeffs.shape[2:]) + + eye = numpy.eye(trans.from_dim, dtype=int) + # construct polynomials for affine transforms of individual dimensions + polys = numpy.zeros((trans.from_dim,)+(2,)*trans.from_dim) + polys[(slice(None),)+(0,)*trans.from_dim] = trans.apply(index, numpy.zeros(trans.from_dim, float))[1] + for idim, e in enumerate(eye): + polys[(slice(None),)+tuple(e)] = trans.basis(index)[:, idim] + # reduces polynomials to smallest nonzero power + polys = [poly[tuple(slice(None if p else 1) for p in poly[tuple(eye)])] for poly in polys] + # construct transform poly by transforming all monomials separately and summing + M = numpy.zeros((degree+1,)*(2*trans.from_dim), dtype=float) + for powers in numpy.ndindex(*[degree+1]*trans.from_dim): + if sum(powers) <= degree: + M_power = functools.reduce(numeric.poly_mul, [numeric.poly_pow(poly, power) for poly, power in zip(polys, powers)]) + M[tuple(slice(n) for n in M_power.shape)+powers] += M_power + + return types.frozenarray(numpy.einsum('jk,ik', M.reshape([(degree+1)**trans.from_dim]*2), coeffs.reshape(coeffs.shape[0], -1)).reshape(coeffs.shape), copy=False) + + class HierarchicalTopology(Topology): 'collection of nested topology elments' @@ -2273,22 +2381,35 @@ def __init__(self, basetopo: Topology, indices_per_level: types.tuple[types.arra level = None levels = [] references = References.empty(basetopo.ndims) - transforms = [] - opposites = [] + coord_system = [] + opposite = [] for indices in self._indices_per_level: level = self.basetopo if level is None else level.refined levels.append(level) if len(indices): references = references.chain(level.references.take(indices)) - transforms.append(level.transforms[indices]) - opposites.append(level.opposites[indices]) + coord_system.append(level.coord_system.take(indices)) + opposite.append(level.opposite.take(indices)) + coord_system = functools.reduce(lambda acc, item: acc.concat(item), coord_system) + opposite = functools.reduce(lambda acc, item: acc.concat(item), opposite) self.levels = tuple(levels) - super().__init__(basetopo.space, references, transformseq.chain(transforms, basetopo.transforms.todims, basetopo.ndims), transformseq.chain(opposites, basetopo.transforms.todims, basetopo.ndims)) + # debug + if debug_flags.hierarchical: + covered = self._indices_per_level[0] + for prev_level, level, indices in zip(self.levels, self.levels[1:], self._indices_per_level[1:]): + trans = level.coord_system.trans_to(prev_level.coord_system) + prev_covered = numpy.unique(numpy.array(trans.unapply_indices(covered), dtype=int)) + covered = numpy.union1d(prev_covered, indices) + assert len(covered) == len(prev_covered) + len(indices) + assert len(covered) == len(self.levels[-1]) + + super().__init__(basetopo.ref_coord_system, references, coord_system, opposite) def __and__(self, other): if not isinstance(other, HierarchicalTopology) or self.basetopo != other.basetopo: return super().__and__(other) + raise NotImplementedError indices_per_level = [] levels = max(self.levels, other.levels, key=len) for level, self_indices, other_indices in itertools.zip_longest(levels, self._indices_per_level, other._indices_per_level, fillvalue=()): @@ -2298,21 +2419,19 @@ def __and__(self, other): for index in indices: # keep common elements or elements which are finer than conterpart keep[index] = mask[index] or topo.transforms.contains_with_tail(level.transforms[index]) indices, = keep.nonzero() - indices_per_level.append(indices) - return HierarchicalTopology(self.basetopo, indices_per_level) - - def _rebase(self, newbasetopo: Topology) -> 'HierarchicalTopology': - itemindices_per_level = [] - for baseindices, baselevel, itemlevel in zip(self._indices_per_level, self.basetopo.refine_iter, newbasetopo.refine_iter): - itemindices = [] - itemindex = itemlevel.transforms.index - for basetrans in map(baselevel.transforms.__getitem__, baseindices): - try: - itemindices.append(itemindex(basetrans)) - except ValueError: - pass - itemindices_per_level.append(numpy.unique(numpy.array(itemindices, dtype=int))) - return HierarchicalTopology(newbasetopo, itemindices_per_level) + indices_per_level.append(types.arraydata(indices)) + return HierarchicalTopology(self.basetopo, tuple(indices_per_level)) + + def _rebase(self, new_base_topo: Topology) -> 'HierarchicalTopology': + new_level = new_base_topo + new_indices_per_level = [] + for old_indices, old_level, new_level in zip(self._indices_per_level, self.levels, new_base_topo.refine_iter): + trans = new_level.coord_system.trans_to(old_level.coord_system) + assert trans.is_index_map + new_indices = numpy.array(trans.unapply_indices(old_indices), dtype=int) + new_indices.sort() + new_indices_per_level.append(types.arraydata(new_indices)) + return HierarchicalTopology(new_base_topo, tuple(new_indices_per_level)) def slice_unchecked(self, __s: slice, __idim: int) -> 'HierarchicalTopology': return self._rebase(self.basetopo.slice_unchecked(__s, __idim)) @@ -2321,66 +2440,58 @@ def get_groups(self, *groups: str) -> 'HierarchicalTopology': return self._rebase(self.basetopo.get_groups(*groups)) def refined_by(self, refine): - refine = tuple(refine) - if not all(map(numeric.isint, refine)): - refine = tuple(self.transforms.index_with_tail(item)[0] for item in refine) - refine = numpy.unique(numpy.array(refine, dtype=int)) + refine = numpy.unique(numpy.asarray(refine, dtype=int)) splits = numpy.searchsorted(refine, self._offsets, side='left') indices_per_level = list(map(list, self._indices_per_level))+[[]] fine = self.basetopo for ilevel, (start, stop) in enumerate(zip(splits[:-1], splits[1:])): coarse, fine = fine, fine.refined coarse_indices = tuple(map(indices_per_level[ilevel].pop, reversed(refine[start:stop]-self._offsets[ilevel]))) - coarse_transforms = map(coarse.transforms.__getitem__, coarse_indices) - coarse_references = map(coarse.references.__getitem__, coarse_indices) - fine_transforms = (trans+(ctrans,) for trans, ref in zip(coarse_transforms, coarse_references) for ctrans, cref in ref.children if cref) - indices_per_level[ilevel+1].extend(map(fine.transforms.index, fine_transforms)) + fine_indices = fine.coord_system.trans_to(coarse.coord_system).unapply_indices(coarse_indices) + indices_per_level[ilevel+1].extend(fine_indices) if not indices_per_level[-1]: indices_per_level.pop(-1) - return HierarchicalTopology(self.basetopo, ([numpy.unique(numpy.array(i, dtype=int)) for i in indices_per_level])) + return HierarchicalTopology(self.basetopo, tuple(types.arraydata(numpy.unique(numpy.array(i, dtype=int))) for i in indices_per_level)) @property def refined(self): - refined_indices_per_level = [numpy.array([], dtype=int)] + refined_indices_per_level = [types.arraydata(numpy.array([], dtype=int))] fine = self.basetopo for coarse_indices in self._indices_per_level: coarse, fine = fine, fine.refined - coarse_transforms = map(coarse.transforms.__getitem__, coarse_indices) - coarse_references = map(coarse.references.__getitem__, coarse_indices) - fine_transforms = (trans+(ctrans,) for trans, ref in zip(coarse_transforms, coarse_references) for ctrans, cref in ref.children if cref) - refined_indices_per_level.append(numpy.unique(numpy.fromiter(map(fine.transforms.index, fine_transforms), dtype=int))) - return HierarchicalTopology(self.basetopo, refined_indices_per_level) + fine_indices = fine.coord_system.trans_to(coarse.coord_system).unapply_indices(coarse_indices) + refined_indices_per_level.append(types.arraydata(numpy.unique(numpy.array(fine_indices, dtype=int)))) + return HierarchicalTopology(self.basetopo, tuple(refined_indices_per_level)) + + def refine_spaces_unchecked(self, __spaces: FrozenSet[str]) -> Topology: + if __spaces == frozenset(self.spaces): + return self.refined + else: + return super().refined_spaces_unchecked(__spaces) @property @log.withcontext def boundary(self): - 'boundary elements' - basebtopo = self.basetopo.boundary bindices_per_level = [] for indices, level, blevel in zip(self._indices_per_level, self.basetopo.refine_iter, basebtopo.refine_iter): - bindex = blevel.transforms.index - bindices = [] - for index in indices: - for etrans, eref in level.references[index].edges: - if eref: - trans = level.transforms[index]+(etrans,) - try: - bindices.append(bindex(trans)) - except ValueError: - pass + bindices = blevel.coord_system.trans_to(level.coord_system).unapply_indices(indices) bindices = numpy.array(bindices, dtype=int) if len(bindices) > 1: bindices.sort() assert not numpy.equal(bindices[1:], bindices[:-1]).any() - bindices_per_level.append(bindices) - return HierarchicalTopology(basebtopo, bindices_per_level) + bindices_per_level.append(types.arraydata(bindices)) + return HierarchicalTopology(basebtopo, tuple(bindices_per_level)) + + def boundary_spaces_unchecked(self, __spaces: FrozenSet[str]) -> 'Topology': + if __spaces == frozenset(self.spaces): + return self.boundary + else: + return super().boundary_spaces_unchecked(__spaces) @property @log.withcontext def interfaces(self): - 'interfaces' - hreferences = References.empty(self.ndims-1) htransforms = [] hopposites = [] @@ -2406,6 +2517,12 @@ def interfaces(self): hopposites.append(level.interfaces.opposites[selection]) return TransformChainsTopology(self.space, hreferences, transformseq.chain(htransforms, self.transforms.todims, self.ndims-1), transformseq.chain(hopposites, self.transforms.todims, self.ndims-1)) + def interfaces_spaces_unchecked(self, __spaces: FrozenSet[str]) -> 'Topology': + if __spaces != frozenset(self.spaces): + return self.interfaces + else: + return super().boundary_spaces_unchecked(__spaces) + @log.withcontext def basis(self, name, *args, truncation_tolerance=1e-15, **kwargs): '''Create hierarchical basis. @@ -2461,18 +2578,24 @@ def basis(self, name, *args, truncation_tolerance=1e-15, **kwargs): ubases = [] ubasis_active = [] ubasis_passive = [] - prev_transforms = None + prev_coord_system = None prev_ielems = [] map_indices = [] + level_trans = [] with log.iter.fraction('level', self.levels[::-1], self._indices_per_level[::-1]) as items: for topo, touchielems_i in items: - topo_index_with_tail = topo.transforms.index_with_tail - mapped_prev_ielems = [topo_index_with_tail(prev_transforms[j])[0] for j in prev_ielems] - map_indices.insert(0, dict(zip(prev_ielems, mapped_prev_ielems))) - nontouchielems_i = numpy.unique(numpy.array(mapped_prev_ielems, dtype=int)) - prev_ielems = ielems_i = numpy.unique(numpy.concatenate([numpy.asarray(touchielems_i, dtype=int), nontouchielems_i], axis=0)) - prev_transforms = topo.transforms + if prev_coord_system is not None: + trans = prev_coord_system.trans_to(topo.coord_system) + level_trans.insert(0, trans) + mapped_prev_ielems = trans.apply_indices(prev_ielems) + map_indices.insert(0, dict(zip(prev_ielems, mapped_prev_ielems))) + nontouchielems_i = numpy.unique(numpy.array(mapped_prev_ielems, dtype=int)) + prev_ielems = ielems_i = numpy.unique(numpy.concatenate([numpy.asarray(touchielems_i, dtype=int), nontouchielems_i], axis=0)) + else: + map_indices.insert(0, {}) + prev_ielems = ielems_i = numpy.asarray(touchielems_i, dtype=int) + prev_coord_system = topo.coord_system basis_i = topo.basis(name, *args, **kwargs) assert isinstance(basis_i, function.Basis) @@ -2496,8 +2619,6 @@ def basis(self, name, *args, truncation_tolerance=1e-15, **kwargs): for ilevel, (level, indices) in enumerate(zip(self.levels, self._indices_per_level)): for ilocal in indices: - hbasis_trans = transform.canonical(level.transforms[ilocal]) - tail = hbasis_trans[len(hbasis_trans)-ilevel:] trans_dofs = [] trans_coeffs = [] @@ -2509,6 +2630,9 @@ def basis(self, name, *args, truncation_tolerance=1e-15, **kwargs): if not truncated: # classical hierarchical basis for h, ilocal in enumerate(local_indices): # loop from coarse to fine + if trans_coeffs: + trans_coeffs = [_transform_poly(level_trans[h - 1], ilocal, c) for c in trans_coeffs] + mydofs = ubases[h].get_dofs(ilocal) imyactive = numeric.sorted_index(ubasis_active[h], mydofs, missing=-1) @@ -2518,17 +2642,18 @@ def basis(self, name, *args, truncation_tolerance=1e-15, **kwargs): mypoly = ubases[h].get_coefficients(ilocal) trans_coeffs.append(mypoly[myactive]) - if h < len(tail): - trans_coeffs = [tail[h].transform_poly(c) for c in trans_coeffs] - else: # truncated hierarchical basis + prev_ilocal = None + for h, ilocal in reversed(tuple(enumerate(local_indices))): # loop from fine to coarse mydofs = ubases[h].get_dofs(ilocal) mypoly = ubases[h].get_coefficients(ilocal) - truncpoly = mypoly if h == len(tail) \ - else numpy.tensordot(numpy.tensordot(tail[h].transform_poly(mypoly), project[..., mypassive], self.ndims), truncpoly[mypassive], 1) + if prev_ilocal is None: + truncpoly = mypoly + else: + truncpoly = numpy.tensordot(numpy.tensordot(_transform_poly(level_trans[h], prev_ilocal, mypoly), project[..., mypassive], self.ndims), truncpoly[mypassive], 1) imyactive = numeric.sorted_index(ubasis_active[h], mydofs, missing=-1) myactive = numpy.greater_equal(imyactive, 0) & numpy.greater(abs(truncpoly), truncation_tolerance).any(axis=tuple(range(1, truncpoly.ndim))) @@ -2548,6 +2673,8 @@ def basis(self, name, *args, truncation_tolerance=1e-15, **kwargs): project = (V.T[:, :len(S)] / S).dot(U.T).reshape(mypoly.shape[1:]+mypoly.shape[:1]) projectcache[id(mypoly)] = project, mypoly # NOTE: mypoly serves to keep array alive + prev_ilocal = ilocal + # add the dofs and coefficients to the hierarchical basis hbasis_dofs.append(numpy.concatenate(trans_dofs)) hbasis_coeffs.append(numeric.poly_concatenate(*trans_coeffs)) @@ -2867,4 +2994,4 @@ def refined(self): return MultipatchTopology(Patch(patch.topo.refined, patch.verts, patch.boundaries) for patch in self.patches) -# vim:sw=2:sts=2:et +# vim:sw=4:sts=4:et diff --git a/src/lib.rs b/src/lib.rs index 5b6f4ef1c..495d019af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -212,6 +212,8 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { let PySliceIndices { start, stop, step, slicelength } = s.indices(len.try_into()?)?; if start == 0 && stop == len && step == 1 { Ok(self.clone()) + } else if step == 1 { + Ok(Self(self.0.slice(start.try_into()?, slicelength.try_into()?)?)) } else { let indices: Vec = (0..slicelength).map(|i| (start + i * step) as usize).collect(); Ok(Self(self.0.take(&indices)?)) @@ -289,12 +291,9 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { self.0.hash(&mut hasher); hasher.finish() } - pub fn __len__(&self) -> usize { - self.0.len_in() - } #[getter] pub fn from_len(&self) -> usize { - self.0.len_out() + self.0.len_in() } #[getter] pub fn to_len(&self) -> usize { @@ -313,6 +312,11 @@ fn _rust(py: Python, m: &PyModule) -> PyResult<()> { .apply_index(index) .ok_or(PyIndexError::new_err("index out of range")) } + pub fn apply_indices(&self, indices: Vec) -> PyResult> { + self.0 + .apply_indices(&indices) + .ok_or(PyIndexError::new_err("index out of range")) + } pub fn apply<'py>( &self, py: Python<'py>, diff --git a/src/map/coord_system.rs b/src/map/coord_system.rs index 0b7d5c8b2..eb243c0e4 100644 --- a/src/map/coord_system.rs +++ b/src/map/coord_system.rs @@ -158,6 +158,37 @@ impl CoordSystem { self.len_out(), )?)) } + pub fn slice(&self, mut start: usize, mut len: usize) -> Result { + if start + len > self.0.len_in() { + return Err(Error::IndexOutOfRange); + } + let mut maps = Vec::new(); + for map in self.0.iter() { + if len == 0 { + break; + } else if start >= map.len_in() { + start -= map.len_in(); + continue; + } else if start == 0 && len >= map.len_in() { + maps.push(map.clone()); + len -= map.len_in(); + } else { + let primitive = Primitive::new_slice(start, len, map.len_in()); + maps.push(map.clone_and_push(primitive).unwrap()); + if start + len <= map.len_in() { + break; + } + len -= map.len_in() - start; + start = 0; + } + } + Ok(Self(UniformConcat::new_unchecked( + maps, + self.dim_in(), + self.delta_dim(), + self.len_out(), + ))) + } pub fn take(&self, indices: &[usize]) -> Result { if !indices.windows(2).all(|pair| pair[0] < pair[1]) { return Err(Error::IndicesNotStrictIncreasing); diff --git a/src/map/mod.rs b/src/map/mod.rs index 7020152dd..9ebccf987 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -36,7 +36,7 @@ impl std::fmt::Display for Error { } /// An interface for an index and coordinate map. -pub trait Map { +pub trait Map: std::fmt::Debug { /// Returns the exclusive upper bound of the indices in the codomain. fn len_out(&self) -> usize; /// Returns the exclusive upper bound of the indices of the domain. diff --git a/src/map/ops.rs b/src/map/ops.rs index dfbcd7f8c..ccf0010dc 100644 --- a/src/map/ops.rs +++ b/src/map/ops.rs @@ -150,7 +150,7 @@ where impl Map for UniformComposition where M: Map, - Array: Deref, + Array: Deref + std::fmt::Debug, { #[inline] fn dim_in(&self) -> usize { @@ -329,7 +329,7 @@ impl Map for BinaryConcat { } #[inline] fn basis_is_constant(&self) -> bool { - self.0.basis_is_constant() && self.1.basis_is_constant() + false } } @@ -391,7 +391,7 @@ where impl Map for UniformConcat where M: Map, - Array: Deref, + Array: Deref + std::fmt::Debug, { #[inline] fn dim_in(&self) -> usize { @@ -456,7 +456,7 @@ where } #[inline] fn is_identity(&self) -> bool { - false + self.maps.len() == 1 && self.maps[0].is_identity() } #[inline] fn is_index_map(&self) -> bool { @@ -474,7 +474,7 @@ where } #[inline] fn basis_is_constant(&self) -> bool { - self.iter().all(|map| map.basis_is_constant()) + self.maps.len() == 1 } } @@ -748,7 +748,7 @@ macro_rules! dispatch { impl Map for UniformProduct where M: Map, - Array: Deref, + Array: Deref + std::fmt::Debug, { dispatch! {fn len_out(&self) -> usize} dispatch! {fn len_in(&self) -> usize} diff --git a/src/map/primitive.rs b/src/map/primitive.rs index a6086295e..5406f6977 100644 --- a/src/map/primitive.rs +++ b/src/map/primitive.rs @@ -6,7 +6,7 @@ use std::ops::{Deref, DerefMut}; use std::sync::Arc; /// An interface for an unbounded coordinate and index map. -pub trait UnboundedMap { +pub trait UnboundedMap: std::fmt::Debug { /// Minimum dimension of the input coordinate. If the dimension of the input /// coordinate of [`UnboundedMap::apply_inplace()`] is larger than the minimum, then /// the map of the surplus is the identity map. @@ -363,10 +363,6 @@ impl Slice { len_out, } } - #[inline] - fn is_identity(&self) -> bool { - self.start == 0 && self.len_in == self.len_out - } } impl UnboundedMap for Slice { @@ -406,6 +402,10 @@ impl UnboundedMap for Slice { .collect() } #[inline] + fn is_identity(&self) -> bool { + self.start == 0 && self.len_in == self.len_out + } + #[inline] fn update_basis(&self, index: usize, _basis: &mut [f64], _dim_out: usize, _dim_in: &mut usize, _offset: usize) -> usize { self.apply_index(index) } @@ -802,7 +802,7 @@ fn comp_mod_out_in(map: &M, mod_out: usize, mod_in: usize) -> ( impl UnboundedMap for Array where M: UnboundedMap, - Array: Deref, + Array: Deref + std::fmt::Debug, { #[inline] fn dim_in(&self) -> usize { diff --git a/src/map/relative.rs b/src/map/relative.rs index a86241740..1eb02210a 100644 --- a/src/map/relative.rs +++ b/src/map/relative.rs @@ -446,15 +446,19 @@ where } else { Relative::Map(rel) }; - return Some(Relative::Composition(UniformComposition::new_unchecked( - vec![slice, rel], - ))); + return Some(if slice.is_identity() { + rel + } else { + Relative::Composition(UniformComposition::new_unchecked( + vec![slice, rel], + )) + }); } PartialRelative::Some(rel, indices) => { rels_indices.push((rel, offset, indices)); } PartialRelative::CannotEstablishRelation => { - return None; + //return None; } } offset = new_offset; @@ -475,19 +479,23 @@ where let mut index_map: Vec> = iter::repeat(None).take(common_len_out).collect(); let mut rels = Vec::new(); - for (irel, (rel, offset, out_indices)) in rels_indices.into_iter().enumerate() { + for (rel, offset, out_indices) in rels_indices.into_iter() { let rel_indices: Vec<_> = (offset..offset + out_indices.len()) .zip(out_indices) .map(|(i, j)| IndexOutIn(i, j)) .collect(); - for IndexOutIn(iout, iin) in rel.unapply_indices_unchecked(&rel_indices) { - assert!( - index_map[iin].is_none(), - "target contains duplicate entries" - ); - index_map[iin] = Some((iout, iin + irel * common_len_out)); + let unapplied = rel.unapply_indices_unchecked(&rel_indices); + if !unapplied.is_empty() { + let rel_offset = rels.len() * common_len_out; + for IndexOutIn(iout, iin) in unapplied { + assert!( + index_map[iin].is_none(), + "target contains duplicate entries" + ); + index_map[iin] = Some((iout, iin + rel_offset)); + } + rels.push(rel); } - rels.push(rel); } index_map .into_iter() diff --git a/src/simplex.rs b/src/simplex.rs index 859e3b8dc..efd69b082 100644 --- a/src/simplex.rs +++ b/src/simplex.rs @@ -279,6 +279,14 @@ impl Simplex { *dim_in += 1; index } + //fn child_poly_coeffs_inplace(&self, index: usize, coeffs: &mut [f64], strides: &[usize], offset: usize) -> usize { + // match self { + // Self::Line => { + // + // } + // _ => unimplemented!{} + // } + //} } #[cfg(test)] From a7b78793173bf976b737580a3f414cfd7b2f5964 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Sat, 30 Jul 2022 23:04:07 +0200 Subject: [PATCH 45/45] WIP --- .github/workflows/test.yaml | 66 ++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0da951fbf..d00ec3946 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,24 +12,36 @@ env: official_container_repository: ghcr.io/evalf/nutils jobs: build-python-package: - name: Build Python package - runs-on: ubuntu-20.04 + name: 'Build Python package on ${{ matrix.os }}' + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - {os: ubuntu-latest} + - {os: macos-latest, build-args: --no-sdist} + - {os: windows-latest, build-args: --no-sdist} steps: - name: Checkout uses: actions/checkout@v2 - - name: Install build dependencies - run: python3 -m pip install setuptools wheel - - name: Build package - run: | - # To make the wheels reproducible, set the timestamp of the (files in - # the) generated wheels to the date of the commit. - export SOURCE_DATE_EPOCH=`git show -s --format=%ct` - python3 setup.py sdist bdist_wheel + - uses: actions/setup-python@v2 + if: matrix.os == 'windows-latest' + with: + python-version: '3.10' + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + default: true + - name: Build wheels + uses: messense/maturin-action@v1 + with: + args: --release ${{ matrix.build-args }} - name: Upload package artifacts uses: actions/upload-artifact@v2 with: name: python-package - path: dist/ + path: target/wheels if-no-files-found: error test: needs: build-python-package @@ -49,7 +61,6 @@ jobs: - {name: "mkl matrix parallel", os: ubuntu-latest, python-version: "3.10", matrix-backend: mkl, nprocs: 2} - {name: "parallel", os: ubuntu-latest, python-version: "3.10", matrix-backend: numpy, nprocs: 2} - {name: "numpy 1.17", os: ubuntu-latest, python-version: "3.7", matrix-backend: numpy, nprocs: 1, numpy-version: ==1.17} - - {name: "tensorial", os: ubuntu-latest, python-version: "3.10", matrix-backend: numpy, nprocs: 1, tensorial: test} fail-fast: false env: NUTILS_MATRIX: ${{ matrix.matrix-backend }} @@ -67,7 +78,7 @@ jobs: - name: Move nutils directory run: mv nutils _nutils - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Download Python package artifact @@ -86,7 +97,7 @@ jobs: python -um pip install --upgrade --upgrade-strategy eager wheel python -um pip install --upgrade --upgrade-strategy eager coverage treelog stringly meshio numpy$_numpy_version # Install Nutils from `dist` dir created in job `build-python-package`. - python -um pip install --no-index --find-links ./dist nutils + python -um pip install --no-index --find-links ./dist --only-binary :all: nutils - name: Install Scipy if: ${{ matrix.matrix-backend == 'scipy' }} run: python -um pip install --upgrade --upgrade-strategy eager scipy @@ -134,21 +145,38 @@ jobs: python -um pip install --upgrade --upgrade-strategy eager wheel python -um pip install --upgrade --upgrade-strategy eager treelog stringly matplotlib scipy pillow numpy git+https://github.com/evalf/nutils-SI.git # Install Nutils from `dist` dir created in job `build-python-package`. - python -um pip install --no-index --find-links ./dist nutils + python -um pip install --no-index --find-links ./dist --only-binary :all: nutils - name: Test run: python -um unittest discover -b -q -t . -s examples test-sphinx: name: Test building docs + needs: build-python-package runs-on: ubuntu-20.04 steps: - name: Checkout uses: actions/checkout@v2 - - name: Install dependencies + - name: Move nutils directory + run: mv nutils _nutils + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: '3.10' + - name: Download Python package artifact + uses: actions/download-artifact@v2 + with: + name: python-package + path: dist/ + - name: Install Nutils and dependencies + id: install + env: + _numpy_version: ${{ matrix.numpy-version }} run: | - python3 -um pip install setuptools wheel - python3 -um pip install --upgrade --upgrade-strategy eager .[docs] + python -um pip install --upgrade --upgrade-strategy eager wheel + python -um pip install --upgrade --upgrade-strategy eager Sphinx treelog stringly meshio numpy scipy matplotlib + # Install Nutils from `dist` dir created in job `build-python-package`. + python -um pip install --no-index --find-links ./dist --only-binary :all: nutils - name: Build docs - run: python3 setup.py build_sphinx --nitpicky --warning-is-error --keep-going + run: python3 -m sphinx -n -W --keep-going docs build/sphinx build-and-test-container-image: name: Build container image needs: build-python-package