Skip to main content

mesh_sieve/algs/
assembly.rs

1//! High-level assembly orchestration across overlaps.
2//!
3//! This module provides helpers to:
4//! 1. reduce/accumulate shared DOFs using ownership metadata,
5//! 2. apply constraints, and
6//! 3. complete (copy) values back to ghosts.
7
8use crate::algs::communicator::{CommTag, Communicator, SectionCommTags};
9use crate::algs::completion::section_completion::complete_section_with_tags_and_ownership;
10use crate::data::constrained_section::{ConstraintSet, apply_constraints_to_section};
11use crate::data::hanging_node_constraints::{
12    HangingNodeConstraints, apply_hanging_constraints_to_section,
13};
14use crate::data::section::Section;
15use crate::data::storage::Storage;
16use crate::mesh_error::MeshSieveError;
17use crate::overlap::delta::{CopyDelta, ValueDelta};
18use crate::overlap::overlap::Overlap;
19use crate::topology::ownership::PointOwnership;
20
21/// Communication tags for the two-phase assembly workflow.
22#[derive(Copy, Clone, Debug)]
23pub struct AssemblyCommTags {
24    /// Tags for the reduction/accumulation phase.
25    pub reduce: SectionCommTags,
26    /// Tags for the owner-to-ghost completion phase.
27    pub complete: SectionCommTags,
28}
29
30impl AssemblyCommTags {
31    /// Construct tags from a base, assigning deterministic offsets per phase.
32    #[inline]
33    pub const fn from_base(base: CommTag) -> Self {
34        Self {
35            reduce: SectionCommTags::from_base(base),
36            complete: SectionCommTags::from_base(base.offset(2)),
37        }
38    }
39}
40
41/// High-level assembly across overlap with ownership and constraints.
42///
43/// # Steps
44/// 1. Reduce/accumulate shared DOFs using `ValueDelta` and ownership metadata.
45/// 2. Apply constraints locally.
46/// 3. Complete (copy) owned values back to ghosts.
47pub fn assemble_section_with_tags_and_ownership<V, S, D, C, Con>(
48    section: &mut Section<V, S>,
49    overlap: &Overlap,
50    ownership: &PointOwnership,
51    comm: &C,
52    my_rank: usize,
53    tags: AssemblyCommTags,
54    constraints: &Con,
55) -> Result<(), MeshSieveError>
56where
57    V: Clone + Default + Send + PartialEq + bytemuck::Pod + Copy + 'static,
58    S: Storage<V>,
59    D: ValueDelta<V> + Send + Sync + 'static,
60    D::Part: bytemuck::Pod + Default + Copy,
61    C: Communicator + Sync,
62    Con: ConstraintSet<V>,
63{
64    complete_section_with_tags_and_ownership::<V, S, D, C>(
65        section,
66        overlap,
67        ownership,
68        comm,
69        my_rank,
70        tags.reduce,
71    )?;
72
73    apply_constraints_to_section(section, constraints)?;
74
75    complete_section_with_tags_and_ownership::<V, S, CopyDelta, C>(
76        section,
77        overlap,
78        ownership,
79        comm,
80        my_rank,
81        tags.complete,
82    )?;
83
84    Ok(())
85}
86
87/// High-level assembly across overlap with ownership, hanging-node constraints,
88/// and fixed constraints. Hanging constraints are applied before fixed values so
89/// Dirichlet-like constraints can override interpolated values on the same DOF.
90pub fn assemble_section_with_hanging_constraints_and_ownership<V, S, D, C, Con>(
91    section: &mut Section<V, S>,
92    overlap: &Overlap,
93    ownership: &PointOwnership,
94    comm: &C,
95    my_rank: usize,
96    tags: AssemblyCommTags,
97    hanging_constraints: &HangingNodeConstraints<V>,
98    constraints: &Con,
99) -> Result<(), MeshSieveError>
100where
101    V: Clone
102        + Default
103        + Send
104        + PartialEq
105        + bytemuck::Pod
106        + Copy
107        + 'static
108        + core::ops::AddAssign
109        + core::ops::Mul<Output = V>,
110    S: Storage<V>,
111    D: ValueDelta<V> + Send + Sync + 'static,
112    D::Part: bytemuck::Pod + Default + Copy,
113    C: Communicator + Sync,
114    Con: ConstraintSet<V>,
115{
116    complete_section_with_tags_and_ownership::<V, S, D, C>(
117        section,
118        overlap,
119        ownership,
120        comm,
121        my_rank,
122        tags.reduce,
123    )?;
124
125    apply_hanging_constraints_to_section(section, hanging_constraints)?;
126    apply_constraints_to_section(section, constraints)?;
127
128    complete_section_with_tags_and_ownership::<V, S, CopyDelta, C>(
129        section,
130        overlap,
131        ownership,
132        comm,
133        my_rank,
134        tags.complete,
135    )?;
136
137    Ok(())
138}
139
140/// Convenience wrapper using a legacy default tag (0xBEEF).
141pub fn assemble_section_with_ownership<V, S, D, C, Con>(
142    section: &mut Section<V, S>,
143    overlap: &Overlap,
144    ownership: &PointOwnership,
145    comm: &C,
146    my_rank: usize,
147    constraints: &Con,
148) -> Result<(), MeshSieveError>
149where
150    V: Clone + Default + Send + PartialEq + bytemuck::Pod + Copy + 'static,
151    S: Storage<V>,
152    D: ValueDelta<V> + Send + Sync + 'static,
153    D::Part: bytemuck::Pod + Default + Copy,
154    C: Communicator + Sync,
155    Con: ConstraintSet<V>,
156{
157    let tags = AssemblyCommTags::from_base(CommTag::new(0xBEEF));
158    assemble_section_with_tags_and_ownership::<V, S, D, C, Con>(
159        section,
160        overlap,
161        ownership,
162        comm,
163        my_rank,
164        tags,
165        constraints,
166    )
167}
168
169use crate::data::closure::{
170    ClosureOrder, IdentitySectionSym, SectionSym, build_closure_index,
171    build_closure_index_unoriented,
172};
173use crate::data::global_map::LocalToGlobalMap;
174use crate::discretization::runtime::{ClosureDof, CsrPattern, DofMap, dof_map_from_closure_index};
175use crate::topology::point::PointId;
176use crate::topology::sieve::{Orientation, OrientedSieve, Sieve};
177use std::collections::{BTreeSet, HashMap};
178
179/// Extract an orientation-correct closure DOF map for one cell.
180pub fn cell_closure_dof_map<T, V, Sct>(
181    topology: &T,
182    section: &Section<V, Sct>,
183    cell: PointId,
184    topology_version: u64,
185    order: &ClosureOrder,
186) -> Result<DofMap, MeshSieveError>
187where
188    T: Sieve<Point = PointId>,
189    Sct: Storage<V>,
190{
191    let index = build_closure_index_unoriented(
192        topology,
193        section,
194        cell,
195        topology_version,
196        order,
197        &IdentitySectionSym,
198    )?;
199    Ok(dof_map_from_closure_index(&index))
200}
201
202/// Extract an oriented closure DOF map using a caller-provided symmetry table.
203pub fn oriented_cell_closure_dof_map<T, V, Sct, O, Sym>(
204    topology: &T,
205    section: &Section<V, Sct>,
206    cell: PointId,
207    topology_version: u64,
208    order: &ClosureOrder,
209    sym: &Sym,
210) -> Result<DofMap, MeshSieveError>
211where
212    T: OrientedSieve<Point = PointId, Orient = O>,
213    Sct: Storage<V>,
214    O: Orientation + Eq + std::hash::Hash,
215    Sym: SectionSym<O>,
216{
217    let index = build_closure_index(topology, section, cell, topology_version, order, sym)?;
218    Ok(dof_map_from_closure_index(&index))
219}
220
221/// Sparse matrix preallocation pattern from cells and a local section.
222pub fn preallocation_csr_from_closure<T, V, Sct>(
223    topology: &T,
224    section: &Section<V, Sct>,
225    cells: impl IntoIterator<Item = PointId>,
226    topology_version: u64,
227    order: &ClosureOrder,
228) -> Result<CsrPattern, MeshSieveError>
229where
230    T: Sieve<Point = PointId>,
231    Sct: Storage<V>,
232{
233    let mut rows: HashMap<ClosureDof, BTreeSet<ClosureDof>> = HashMap::new();
234    for cell in cells {
235        let map = cell_closure_dof_map(topology, section, cell, topology_version, order)?;
236        let dofs = map.closure_dofs().to_vec();
237        for row in &dofs {
238            rows.entry(*row).or_default().extend(dofs.iter().copied());
239        }
240    }
241    Ok(csr_from_rows(rows))
242}
243
244/// Sparse matrix preallocation pattern in global-numbered CSR columns.
245pub fn global_preallocation_csr_from_closure<T, V, Sct>(
246    topology: &T,
247    section: &Section<V, Sct>,
248    global_map: &LocalToGlobalMap,
249    cells: impl IntoIterator<Item = PointId>,
250    topology_version: u64,
251    order: &ClosureOrder,
252) -> Result<GlobalCsrPattern, MeshSieveError>
253where
254    T: Sieve<Point = PointId>,
255    Sct: Storage<V>,
256{
257    let local = preallocation_csr_from_closure(topology, section, cells, topology_version, order)?;
258    let rows = local
259        .rows
260        .iter()
261        .map(|dof| {
262            global_map
263                .global_index(dof.point, dof.local_dof)
264                .map(|g| g as usize)
265        })
266        .collect::<Result<Vec<_>, _>>()?;
267    let adjncy = local
268        .adjncy
269        .iter()
270        .map(|dof| {
271            global_map
272                .global_index(dof.point, dof.local_dof)
273                .map(|g| g as usize)
274        })
275        .collect::<Result<Vec<_>, _>>()?;
276    Ok(GlobalCsrPattern {
277        xadj: local.xadj,
278        adjncy,
279        rows,
280    })
281}
282
283/// Global-numbered CSR sparsity pattern for solver matrix preallocation.
284#[derive(Clone, Debug, PartialEq, Eq)]
285pub struct GlobalCsrPattern {
286    /// CSR row offsets.
287    pub xadj: Vec<usize>,
288    /// Global column indices.
289    pub adjncy: Vec<usize>,
290    /// Global row indices represented by each CSR row.
291    pub rows: Vec<usize>,
292}
293
294fn csr_from_rows(mut rows: HashMap<ClosureDof, BTreeSet<ClosureDof>>) -> CsrPattern {
295    let mut row_dofs: Vec<_> = rows.keys().copied().collect();
296    row_dofs.sort_unstable();
297    let mut xadj = Vec::with_capacity(row_dofs.len() + 1);
298    let mut adjncy = Vec::new();
299    xadj.push(0);
300    for row in &row_dofs {
301        if let Some(cols) = rows.remove(row) {
302            adjncy.extend(cols);
303        }
304        xadj.push(adjncy.len());
305    }
306    CsrPattern {
307        xadj,
308        adjncy,
309        rows: row_dofs,
310    }
311}