use crate::mesh_error::MeshSieveError;
use crate::topology::point::PointId;
use crate::topology::sieve::Sieve;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Default, Clone)]
pub struct PointEquivalence {
parent: BTreeMap<PointId, PointId>,
rank: BTreeMap<PointId, u32>,
}
impl PointEquivalence {
pub fn new() -> Self {
Self::default()
}
pub fn with_points<I>(points: I) -> Self
where
I: IntoIterator<Item = PointId>,
{
let mut eq = Self::default();
for p in points {
eq.add_point(p);
}
eq
}
pub fn add_point(&mut self, point: PointId) {
self.parent.entry(point).or_insert(point);
self.rank.entry(point).or_insert(0);
}
pub fn points(&self) -> impl Iterator<Item = PointId> + '_ {
self.parent.keys().copied()
}
fn find_root(&mut self, point: PointId) -> PointId {
if !self.parent.contains_key(&point) {
self.add_point(point);
return point;
}
let parent = self.parent[&point];
if parent == point {
return point;
}
let root = self.find_root(parent);
self.parent.insert(point, root);
root
}
pub fn representative(&mut self, point: PointId) -> PointId {
self.find_root(point)
}
pub fn union(&mut self, a: PointId, b: PointId) -> PointId {
let ra = self.find_root(a);
let rb = self.find_root(b);
if ra == rb {
return ra;
}
let rank_a = self.rank.get(&ra).copied().unwrap_or(0);
let rank_b = self.rank.get(&rb).copied().unwrap_or(0);
if rank_a < rank_b {
self.parent.insert(ra, rb);
rb
} else {
self.parent.insert(rb, ra);
if rank_a == rank_b {
self.rank.insert(ra, rank_a + 1);
}
ra
}
}
pub fn add_equivalence(&mut self, a: PointId, b: PointId) {
self.union(a, b);
}
pub fn are_equivalent(&mut self, a: PointId, b: PointId) -> bool {
self.find_root(a) == self.find_root(b)
}
pub fn canonical_map<I>(&mut self, points: I) -> BTreeMap<PointId, PointId>
where
I: IntoIterator<Item = PointId>,
{
let mut map = BTreeMap::new();
for p in points {
map.insert(p, self.find_root(p));
}
map
}
pub fn classes<I>(&mut self, points: I) -> BTreeMap<PointId, Vec<PointId>>
where
I: IntoIterator<Item = PointId>,
{
let mut classes: BTreeMap<PointId, Vec<PointId>> = BTreeMap::new();
for p in points {
let rep = self.find_root(p);
classes.entry(rep).or_default().push(p);
}
classes
}
pub fn representatives(&mut self) -> BTreeSet<PointId> {
let points: Vec<_> = self.points().collect();
points.into_iter().map(|p| self.find_root(p)).collect()
}
}
#[derive(Debug, Default, Clone)]
pub struct PeriodicMap {
master_for: BTreeMap<PointId, PointId>,
}
impl PeriodicMap {
pub fn new() -> Self {
Self::default()
}
pub fn insert_pair(&mut self, master: PointId, slave: PointId) -> Result<(), MeshSieveError> {
if let Some(existing) = self.master_for.get(&slave)
&& *existing != master
{
return Err(MeshSieveError::PeriodicMappingConflict {
slave,
existing: *existing,
new: master,
});
}
self.master_for.insert(slave, master);
Ok(())
}
pub fn master_of(&self, slave: PointId) -> Option<PointId> {
self.master_for.get(&slave).copied()
}
pub fn pairs(&self) -> impl Iterator<Item = (PointId, PointId)> + '_ {
self.master_for
.iter()
.map(|(slave, master)| (*master, *slave))
}
pub fn equivalence(&self) -> PointEquivalence {
let mut eq = PointEquivalence::new();
for (master, slave) in self.pairs() {
eq.add_equivalence(master, slave);
}
eq
}
}
pub fn collapse_points<I>(
points: I,
equivalence: &mut PointEquivalence,
) -> BTreeMap<PointId, PointId>
where
I: IntoIterator<Item = PointId>,
{
equivalence.canonical_map(points)
}
pub fn quotient_sieve<S>(sieve: &S, equivalence: &mut PointEquivalence) -> S
where
S: Sieve<Point = PointId> + Default,
S::Payload: Clone,
{
let mut quotient = S::default();
for src in sieve.base_points() {
let rep_src = equivalence.representative(src);
for (dst, payload) in sieve.cone(src) {
let rep_dst = equivalence.representative(dst);
if rep_src == rep_dst {
continue;
}
quotient.add_arrow(rep_src, rep_dst, payload.clone());
}
}
quotient
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equivalence_groups_points() {
let a = PointId::new(1).unwrap();
let b = PointId::new(2).unwrap();
let c = PointId::new(3).unwrap();
let mut eq = PointEquivalence::new();
eq.add_equivalence(a, b);
assert!(eq.are_equivalent(a, b));
assert!(!eq.are_equivalent(a, c));
eq.add_equivalence(b, c);
assert!(eq.are_equivalent(a, c));
}
#[test]
fn periodic_map_rejects_conflicts() {
let master_a = PointId::new(1).unwrap();
let master_b = PointId::new(2).unwrap();
let slave = PointId::new(3).unwrap();
let mut map = PeriodicMap::new();
map.insert_pair(master_a, slave).unwrap();
let err = map.insert_pair(master_b, slave).unwrap_err();
match err {
MeshSieveError::PeriodicMappingConflict { slave: s, .. } => {
assert_eq!(s, slave);
}
_ => panic!("unexpected error"),
}
}
}