Skip to main content

mesh_sieve/io/
bundle.rs

1//! Mesh bundle container for multiple mesh topologies.
2
3use std::collections::{BTreeMap, HashSet};
4
5use crate::data::storage::Storage;
6use crate::mesh_error::MeshSieveError;
7use crate::topology::cell_type::CellType;
8use crate::topology::labels::LabelSet;
9use crate::topology::point::PointId;
10use crate::topology::sieve::Sieve;
11
12use super::MeshData;
13
14/// Collection of mesh topologies with separate section data.
15#[derive(Debug)]
16pub struct MeshBundle<S, V, St, CtSt>
17where
18    St: Storage<V>,
19    CtSt: Storage<CellType>,
20{
21    /// Individual mesh containers.
22    pub meshes: Vec<MeshData<S, V, St, CtSt>>,
23}
24
25impl<S, V, St, CtSt> MeshBundle<S, V, St, CtSt>
26where
27    St: Storage<V>,
28    CtSt: Storage<CellType>,
29{
30    /// Construct a bundle from a list of meshes.
31    pub fn new(meshes: Vec<MeshData<S, V, St, CtSt>>) -> Self {
32        Self { meshes }
33    }
34
35    /// Add a mesh to the bundle.
36    pub fn push(&mut self, mesh: MeshData<S, V, St, CtSt>) {
37        self.meshes.push(mesh);
38    }
39
40    /// Borrow all meshes immutably.
41    pub fn meshes(&self) -> &[MeshData<S, V, St, CtSt>] {
42        &self.meshes
43    }
44
45    /// Borrow all meshes mutably.
46    pub fn meshes_mut(&mut self) -> &mut [MeshData<S, V, St, CtSt>] {
47        &mut self.meshes
48    }
49}
50
51impl<S, V, St, CtSt> MeshBundle<S, V, St, CtSt>
52where
53    S: Sieve<Point = PointId>,
54    St: Storage<V>,
55    CtSt: Storage<CellType>,
56{
57    /// Synchronize label entries for shared points across all meshes.
58    ///
59    /// For every label entry appearing in any mesh, this updates every other
60    /// mesh that contains the same point to carry that label value.
61    ///
62    /// Returns the number of label entries applied across meshes.
63    pub fn sync_labels(&mut self) -> usize {
64        let mut combined: Vec<(String, PointId, i32)> = Vec::new();
65        for mesh in &self.meshes {
66            if let Some(labels) = &mesh.labels {
67                for (name, point, value) in labels.iter() {
68                    combined.push((name.to_string(), point, value));
69                }
70            }
71        }
72
73        combined.sort_by(|a, b| {
74            a.0.cmp(&b.0)
75                .then_with(|| a.1.cmp(&b.1))
76                .then_with(|| a.2.cmp(&b.2))
77        });
78
79        let mut applied = 0usize;
80        for mesh in &mut self.meshes {
81            if combined.is_empty() {
82                break;
83            }
84            let points: HashSet<PointId> = mesh.sieve.points().collect();
85            if points.is_empty() {
86                continue;
87            }
88            let labels = mesh.labels.get_or_insert_with(LabelSet::new);
89            for (name, point, value) in &combined {
90                if points.contains(point) {
91                    labels.set_label(*point, name, *value);
92                    applied += 1;
93                }
94            }
95        }
96
97        applied
98    }
99}
100
101impl<S, V, St, CtSt> MeshBundle<S, V, St, CtSt>
102where
103    S: Sieve<Point = PointId>,
104    V: Clone + Default + PartialEq,
105    St: Storage<V> + Clone,
106    CtSt: Storage<CellType>,
107{
108    /// Synchronize coordinate values for shared points across all meshes.
109    ///
110    /// Returns an error if coordinate dimensions differ or if conflicting
111    /// values are detected for the same point.
112    pub fn sync_coordinates(&mut self) -> Result<(), MeshSieveError> {
113        let mut dimension: Option<usize> = None;
114        let mut values: BTreeMap<PointId, Vec<V>> = BTreeMap::new();
115
116        for mesh in &self.meshes {
117            let Some(coords) = &mesh.coordinates else {
118                continue;
119            };
120            if let Some(existing_dim) = dimension {
121                if coords.dimension() != existing_dim {
122                    return Err(MeshSieveError::InvalidGeometry(format!(
123                        "coordinate dimension mismatch: expected {existing_dim}, found {}",
124                        coords.dimension()
125                    )));
126                }
127            } else {
128                dimension = Some(coords.dimension());
129            }
130
131            for point in coords.section().atlas().points() {
132                let slice = coords.try_restrict(point)?;
133                if let Some(existing) = values.get(&point) {
134                    if existing.as_slice() != slice {
135                        return Err(MeshSieveError::InvalidGeometry(format!(
136                            "conflicting coordinate values for point {point:?}"
137                        )));
138                    }
139                } else {
140                    values.insert(point, slice.to_vec());
141                }
142            }
143        }
144
145        if values.is_empty() {
146            return Ok(());
147        }
148
149        for mesh in &mut self.meshes {
150            let Some(coords) = &mut mesh.coordinates else {
151                continue;
152            };
153            let points: Vec<_> = coords.section().atlas().points().collect();
154            for point in points {
155                if let Some(val) = values.get(&point) {
156                    coords.section_mut().try_set(point, val)?;
157                }
158            }
159        }
160
161        Ok(())
162    }
163}