Skip to main content

mesh_sieve/
mesh_graph.rs

1//! Mesh graph exports for adjacency (cell/vertex) in CSR or edge-list form.
2//!
3//! This module provides lightweight wrappers around the adjacency builders so
4//! callers can obtain CSR adjacency with optional shared-boundary weights, or
5//! undirected edge lists for cell adjacency.
6
7use std::collections::HashMap;
8
9use crate::algs::adjacency_graph::{
10    AdjacencyOrdering, CellAdjacencyEdges, CellAdjacencyOpts, VertexAdjacencyOpts,
11    build_cell_adjacency_edges, build_cell_adjacency_graph_with_cells,
12    build_vertex_adjacency_graph_with_vertices,
13};
14use crate::mesh_error::MeshSieveError;
15use crate::topology::point::PointId;
16use crate::topology::sieve::Sieve;
17use crate::topology::sieve::strata::compute_strata;
18
19/// Weighting mode for adjacency graphs.
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum AdjacencyWeighting {
22    /// No weights (unweighted adjacency).
23    None,
24    /// Weight edges by the number of shared boundary entities.
25    SharedBoundaryCount,
26}
27
28/// CSR-style adjacency graph with optional weights.
29#[derive(Debug, Clone)]
30pub struct MeshGraph {
31    /// CSR offsets into `adjncy` for each vertex.
32    pub xadj: Vec<usize>,
33    /// CSR adjacency list (indices into `order`).
34    pub adjncy: Vec<usize>,
35    /// Point ordering that defines vertex indices.
36    pub order: Vec<PointId>,
37    /// Optional per-edge weights aligned with `adjncy`.
38    pub weights: Option<Vec<u32>>,
39}
40
41impl MeshGraph {
42    /// Return the neighbor index slice for vertex `i`.
43    #[inline]
44    pub fn neighbors(&self, i: usize) -> &[usize] {
45        &self.adjncy[self.xadj[i]..self.xadj[i + 1]]
46    }
47
48    /// Return the neighbor weight slice for vertex `i`, if present.
49    #[inline]
50    pub fn neighbor_weights(&self, i: usize) -> Option<&[u32]> {
51        self.weights
52            .as_ref()
53            .map(|w| &w[self.xadj[i]..self.xadj[i + 1]])
54    }
55}
56
57/// Build a cell-to-cell adjacency graph over all height-0 cells.
58pub fn cell_adjacency_graph<S>(
59    sieve: &S,
60    opts: CellAdjacencyOpts,
61    weighting: AdjacencyWeighting,
62) -> Result<MeshGraph, MeshSieveError>
63where
64    S: Sieve<Point = PointId>,
65{
66    let strata = compute_strata(sieve)?;
67    let cells = strata.strata.first().cloned().unwrap_or_default();
68    Ok(cell_adjacency_graph_with_cells(
69        sieve, cells, opts, weighting,
70    ))
71}
72
73/// Build a cell-to-cell adjacency graph for a provided cell list.
74pub fn cell_adjacency_graph_with_cells<S>(
75    sieve: &S,
76    cells: impl IntoIterator<Item = PointId>,
77    opts: CellAdjacencyOpts,
78    weighting: AdjacencyWeighting,
79) -> MeshGraph
80where
81    S: Sieve<Point = PointId>,
82{
83    let cells: Vec<PointId> = cells.into_iter().collect();
84    if matches!(weighting, AdjacencyWeighting::None) {
85        let graph = build_cell_adjacency_graph_with_cells(sieve, cells, opts);
86        return MeshGraph {
87            xadj: graph.xadj,
88            adjncy: graph.adjncy,
89            order: graph.order,
90            weights: None,
91        };
92    }
93
94    let cells = order_points(cells, opts.ordering);
95    build_weighted_shared_boundary_graph(
96        &cells,
97        |p| downward_boundary_points(sieve, p, opts.boundary.max_down_depth),
98        opts.symmetrize,
99    )
100}
101
102/// Build an undirected edge list for a provided cell list.
103pub fn cell_adjacency_edges_for_cells<S>(
104    sieve: &S,
105    cells: impl IntoIterator<Item = PointId>,
106    cell_dimension: u32,
107    by: crate::algs::adjacency_graph::CellAdjacencyBy,
108    ordering: AdjacencyOrdering,
109) -> CellAdjacencyEdges
110where
111    S: Sieve<Point = PointId>,
112{
113    build_cell_adjacency_edges(sieve, cells, cell_dimension, by, ordering)
114}
115
116/// Build a vertex-to-vertex adjacency graph over all depth-0 vertices.
117pub fn vertex_adjacency_graph<S>(
118    sieve: &S,
119    opts: VertexAdjacencyOpts,
120    weighting: AdjacencyWeighting,
121) -> Result<MeshGraph, MeshSieveError>
122where
123    S: Sieve<Point = PointId>,
124{
125    let strata = compute_strata(sieve)?;
126    let vertices: Vec<PointId> = strata
127        .chart_points
128        .iter()
129        .copied()
130        .filter(|p| strata.depth.get(p).copied() == Some(0))
131        .collect();
132    Ok(vertex_adjacency_graph_with_vertices(
133        sieve, vertices, opts, weighting,
134    ))
135}
136
137/// Build a vertex-to-vertex adjacency graph for a provided vertex list.
138pub fn vertex_adjacency_graph_with_vertices<S>(
139    sieve: &S,
140    vertices: impl IntoIterator<Item = PointId>,
141    opts: VertexAdjacencyOpts,
142    weighting: AdjacencyWeighting,
143) -> MeshGraph
144where
145    S: Sieve<Point = PointId>,
146{
147    let vertices: Vec<PointId> = vertices.into_iter().collect();
148    if matches!(weighting, AdjacencyWeighting::None) {
149        let graph = build_vertex_adjacency_graph_with_vertices(sieve, vertices, opts);
150        return MeshGraph {
151            xadj: graph.xadj,
152            adjncy: graph.adjncy,
153            order: graph.order,
154            weights: None,
155        };
156    }
157
158    let vertices = order_points(vertices, opts.ordering);
159    build_weighted_shared_boundary_graph(
160        &vertices,
161        |p| upward_boundary_points(sieve, p, opts.max_up_depth),
162        opts.symmetrize,
163    )
164}
165
166fn order_points<I>(points: I, ordering: AdjacencyOrdering) -> Vec<PointId>
167where
168    I: IntoIterator<Item = PointId>,
169{
170    let mut out: Vec<PointId> = points.into_iter().collect();
171    match ordering {
172        AdjacencyOrdering::Input => {
173            let mut seen = std::collections::HashSet::with_capacity(out.len());
174            out.retain(|p| seen.insert(*p));
175        }
176        AdjacencyOrdering::Sorted => {
177            out.sort_unstable();
178            out.dedup();
179        }
180    }
181    out
182}
183
184fn downward_boundary_points<S>(sieve: &S, p: PointId, max_down_depth: Option<u32>) -> Vec<PointId>
185where
186    S: Sieve<Point = PointId>,
187{
188    use std::collections::{HashSet, VecDeque};
189
190    match max_down_depth {
191        Some(0) => Vec::new(),
192        Some(1) => {
193            let mut out: Vec<PointId> = sieve.cone_points(p).collect();
194            out.sort_unstable();
195            out.dedup();
196            out
197        }
198        None | Some(_) => {
199            let limit = max_down_depth.unwrap_or(u32::MAX);
200            let mut out = Vec::new();
201            let mut seen: HashSet<PointId> = HashSet::new();
202            let mut q: VecDeque<(PointId, u32)> = VecDeque::new();
203            q.extend(sieve.cone_points(p).map(|x| (x, 1)));
204            while let Some((r, d)) = q.pop_front() {
205                if seen.insert(r) {
206                    out.push(r);
207                    if d < limit {
208                        for s in sieve.cone_points(r) {
209                            if !seen.contains(&s) {
210                                q.push_back((s, d + 1));
211                            }
212                        }
213                    }
214                }
215            }
216            out.sort_unstable();
217            out.dedup();
218            out
219        }
220    }
221}
222
223fn upward_boundary_points<S>(sieve: &S, p: PointId, max_up_depth: Option<u32>) -> Vec<PointId>
224where
225    S: Sieve<Point = PointId>,
226{
227    use std::collections::{HashSet, VecDeque};
228
229    match max_up_depth {
230        Some(0) => Vec::new(),
231        Some(1) => {
232            let mut out: Vec<PointId> = sieve.support_points(p).collect();
233            out.sort_unstable();
234            out.dedup();
235            out
236        }
237        None | Some(_) => {
238            let limit = max_up_depth.unwrap_or(u32::MAX);
239            let mut out = Vec::new();
240            let mut seen: HashSet<PointId> = HashSet::new();
241            let mut q: VecDeque<(PointId, u32)> = VecDeque::new();
242            q.extend(sieve.support_points(p).map(|x| (x, 1)));
243            while let Some((r, d)) = q.pop_front() {
244                if seen.insert(r) {
245                    out.push(r);
246                    if d < limit {
247                        for s in sieve.support_points(r) {
248                            if !seen.contains(&s) {
249                                q.push_back((s, d + 1));
250                            }
251                        }
252                    }
253                }
254            }
255            out.sort_unstable();
256            out.dedup();
257            out
258        }
259    }
260}
261
262fn build_weighted_shared_boundary_graph(
263    points: &[PointId],
264    boundary: impl Fn(PointId) -> Vec<PointId>,
265    symmetrize: bool,
266) -> MeshGraph {
267    let n = points.len();
268    if n == 0 {
269        return MeshGraph {
270            xadj: vec![0],
271            adjncy: Vec::new(),
272            order: Vec::new(),
273            weights: Some(Vec::new()),
274        };
275    }
276
277    let mut incident: HashMap<PointId, Vec<usize>> = HashMap::new();
278    incident.reserve(n * 4);
279    for (i, &p) in points.iter().enumerate() {
280        for b in boundary(p) {
281            incident.entry(b).or_default().push(i);
282        }
283    }
284
285    let mut neigh: Vec<HashMap<usize, u32>> = vec![HashMap::new(); n];
286    for (_b, mut verts_on_b) in incident {
287        if verts_on_b.len() < 2 {
288            continue;
289        }
290        verts_on_b.sort_unstable();
291        verts_on_b.dedup();
292        for i in 0..verts_on_b.len() {
293            let vi = verts_on_b[i];
294            for &vj in &verts_on_b[(i + 1)..] {
295                if vi == vj {
296                    continue;
297                }
298                *neigh[vi].entry(vj).or_insert(0) += 1;
299                if symmetrize {
300                    *neigh[vj].entry(vi).or_insert(0) += 1;
301                }
302            }
303        }
304    }
305
306    let mut xadj = Vec::with_capacity(n + 1);
307    let mut adjncy = Vec::new();
308    let mut weights = Vec::new();
309    xadj.push(0);
310    for (i, map) in neigh.into_iter().enumerate() {
311        let mut neighbors: Vec<(usize, u32)> = map.into_iter().collect();
312        neighbors.sort_by_key(|(idx, _)| *idx);
313        if let Some(pos) = neighbors.iter().position(|(idx, _)| *idx == i) {
314            neighbors.remove(pos);
315        }
316        adjncy.extend(neighbors.iter().map(|(idx, _)| *idx));
317        weights.extend(neighbors.iter().map(|(_, w)| *w));
318        xadj.push(adjncy.len());
319    }
320
321    MeshGraph {
322        xadj,
323        adjncy,
324        order: points.to_vec(),
325        weights: Some(weights),
326    }
327}