use crate::data::atlas::Atlas;
use crate::data::coordinates::Coordinates;
use crate::data::section::Section;
use crate::data::storage::Storage;
use crate::mesh_error::MeshSieveError;
use crate::topology::labels::LabelSet;
use crate::topology::point::PointId;
use crate::topology::refine::RefinementMap;
use crate::topology::sieve::Sieve;
use std::collections::{HashMap, HashSet};
pub fn transfer_section_by_refinement_map<V, S>(
coarse: &Section<V, S>,
refinement: &RefinementMap,
) -> Result<Section<V, S>, MeshSieveError>
where
V: Clone + Default,
S: Storage<V> + Clone,
{
let mut atlas = Atlas::default();
let mut seen = HashSet::new();
for (coarse_point, fine_points) in refinement {
let (_, len) = coarse
.atlas()
.get(*coarse_point)
.ok_or(MeshSieveError::MissingSectionPoint(*coarse_point))?;
for (fine_point, _) in fine_points {
if !seen.insert(*fine_point) {
return Err(MeshSieveError::DuplicateRefinementTarget { fine: *fine_point });
}
atlas.try_insert(*fine_point, len)?;
}
}
let mut out = Section::new(atlas);
for (coarse_point, fine_points) in refinement {
let data = coarse.try_restrict(*coarse_point)?;
for (fine_point, _) in fine_points {
out.try_set(*fine_point, data)?;
}
}
Ok(out)
}
pub fn transfer_section_by_shared_labels<V, S>(
source: &Section<V, S>,
source_labels: &LabelSet,
target_labels: &LabelSet,
label_name: &str,
) -> Result<Section<V, S>, MeshSieveError>
where
V: Clone + Default,
S: Storage<V> + Clone,
{
let mut label_to_point = HashMap::new();
for (name, point, value) in source_labels.iter() {
if name == label_name && label_to_point.insert(value, point).is_some() {
return Err(MeshSieveError::InvalidGeometry(format!(
"duplicate source label value {value} for label '{label_name}'"
)));
}
}
let mut atlas = Atlas::default();
let mut transfers = Vec::new();
for (name, target_point, value) in target_labels.iter() {
if name != label_name {
continue;
}
let source_point = label_to_point.get(&value).ok_or_else(|| {
MeshSieveError::InvalidGeometry(format!(
"missing source label value {value} for label '{label_name}'"
))
})?;
let (_, len) = source
.atlas()
.get(*source_point)
.ok_or(MeshSieveError::MissingSectionPoint(*source_point))?;
atlas.try_insert(target_point, len)?;
transfers.push((target_point, *source_point));
}
let mut out = Section::new(atlas);
for (target_point, source_point) in transfers {
let data = source.try_restrict(source_point)?;
out.try_set(target_point, data)?;
}
Ok(out)
}
pub fn transfer_section_by_nearest_point<V, S, Cs>(
source: &Section<V, S>,
source_coords: &Coordinates<f64, Cs>,
target_coords: &Coordinates<f64, Cs>,
) -> Result<Section<V, S>, MeshSieveError>
where
V: Clone + Default,
S: Storage<V> + Clone,
Cs: Storage<f64>,
{
let dimension = source_coords.dimension();
if dimension != target_coords.dimension() {
return Err(MeshSieveError::InvalidGeometry(format!(
"coordinate dimension mismatch: source {dimension}, target {}",
target_coords.dimension()
)));
}
let source_points: Vec<PointId> = source.atlas().points().collect();
if source_points.is_empty() {
return Err(MeshSieveError::InvalidGeometry(
"nearest-point transfer requires at least one source point".into(),
));
}
let mut transfers = Vec::new();
for target_point in target_coords.section().atlas().points() {
let target_pos = target_coords.try_restrict(target_point)?;
let mut best = None;
for source_point in &source_points {
let source_pos = source_coords.try_restrict(*source_point)?;
let dist = squared_distance(source_pos, target_pos);
match best {
Some((best_dist, _)) if dist >= best_dist => {}
_ => best = Some((dist, *source_point)),
}
}
let source_point = best
.map(|(_, point)| point)
.expect("source_points is non-empty");
transfers.push((target_point, source_point));
}
let mut atlas = Atlas::default();
for (target_point, source_point) in &transfers {
let (_, len) = source
.atlas()
.get(*source_point)
.ok_or(MeshSieveError::MissingSectionPoint(*source_point))?;
atlas.try_insert(*target_point, len)?;
}
let mut out = Section::new(atlas);
for (target_point, source_point) in transfers {
let data = source.try_restrict(source_point)?;
out.try_set(target_point, data)?;
}
Ok(out)
}
pub fn transfer_section_by_nearest_cell_centroid<V, S, Cs, Ss, Ts>(
source: &Section<V, S>,
source_sieve: &Ss,
source_coords: &Coordinates<f64, Cs>,
target_sieve: &Ts,
target_coords: &Coordinates<f64, Cs>,
target_cells: impl IntoIterator<Item = PointId>,
) -> Result<Section<V, S>, MeshSieveError>
where
V: Clone + Default,
S: Storage<V> + Clone,
Cs: Storage<f64>,
Ss: Sieve<Point = PointId>,
Ts: Sieve<Point = PointId>,
{
let dimension = source_coords.dimension();
if dimension != target_coords.dimension() {
return Err(MeshSieveError::InvalidGeometry(format!(
"coordinate dimension mismatch: source {dimension}, target {}",
target_coords.dimension()
)));
}
let mut source_centroids = Vec::new();
for point in source.atlas().points() {
let centroid = centroid_from_closure(source_sieve, source_coords, point)?;
source_centroids.push((point, centroid));
}
if source_centroids.is_empty() {
return Err(MeshSieveError::InvalidGeometry(
"nearest-cell transfer requires at least one source cell".into(),
));
}
let mut transfers = Vec::new();
for target_cell in target_cells {
let target_centroid = centroid_from_closure(target_sieve, target_coords, target_cell)?;
let mut best = None;
for (source_cell, source_centroid) in &source_centroids {
let dist = squared_distance(&target_centroid, source_centroid);
match best {
Some((best_dist, _)) if dist >= best_dist => {}
_ => best = Some((dist, *source_cell)),
}
}
let source_cell = best
.map(|(_, cell)| cell)
.expect("source_centroids is non-empty");
transfers.push((target_cell, source_cell));
}
let mut atlas = Atlas::default();
for (target_cell, source_cell) in &transfers {
let (_, len) = source
.atlas()
.get(*source_cell)
.ok_or(MeshSieveError::MissingSectionPoint(*source_cell))?;
atlas.try_insert(*target_cell, len)?;
}
let mut out = Section::new(atlas);
for (target_cell, source_cell) in transfers {
let data = source.try_restrict(source_cell)?;
out.try_set(target_cell, data)?;
}
Ok(out)
}
fn squared_distance(a: &[f64], b: &[f64]) -> f64 {
a.iter()
.zip(b.iter())
.map(|(lhs, rhs)| {
let diff = lhs - rhs;
diff * diff
})
.sum()
}
fn centroid_from_closure<S, Cs>(
sieve: &S,
coords: &Coordinates<f64, Cs>,
point: PointId,
) -> Result<Vec<f64>, MeshSieveError>
where
S: Sieve<Point = PointId>,
Cs: Storage<f64>,
{
let dimension = coords.dimension();
if coords.section().atlas().contains(point) {
return Ok(coords.try_restrict(point)?.to_vec());
}
let mut sum = vec![0.0; dimension];
let mut count = 0usize;
for p in sieve.closure_iter([point]) {
if coords.section().atlas().contains(p) {
let slice = coords.try_restrict(p)?;
for (idx, value) in slice.iter().enumerate() {
sum[idx] += *value;
}
count += 1;
}
}
if count == 0 {
return Err(MeshSieveError::InvalidGeometry(format!(
"no coordinate-bearing points found in closure of {point:?}"
)));
}
let inv = 1.0 / count as f64;
for value in &mut sum {
*value *= inv;
}
Ok(sum)
}