1use crate::data::closure::{
4 ClosureOrder, IdentitySectionSym, SectionSym, build_closure_index,
5 build_closure_index_unoriented, get_closure,
6};
7use crate::data::coordinates::Coordinates;
8use crate::data::discretization::DiscretizationMetadata;
9use crate::data::global_map::LocalToGlobalMap;
10use crate::data::section::Section;
11use crate::data::storage::Storage;
12use crate::discretization::runtime::{
13 Basis, BasisTabulation, QuadratureRule, ensure_geometry_order_supported, local_load_vector,
14 local_stiffness_matrix, runtime_from_metadata, tabulate_element,
15};
16use crate::mesh_error::MeshSieveError;
17use crate::topology::cell_type::CellType;
18use crate::topology::point::PointId;
19use crate::topology::sieve::{Orientation, OrientedSieve, Sieve};
20
21#[derive(Clone, Debug)]
23pub struct ReferenceElementEvaluation {
24 pub basis: Basis,
26 pub quadrature: QuadratureRule,
28 pub tabulation: BasisTabulation,
30}
31
32pub fn evaluate_reference_element(
34 cell_type: CellType,
35 metadata: &DiscretizationMetadata,
36) -> Result<ReferenceElementEvaluation, MeshSieveError> {
37 let runtime = runtime_from_metadata(metadata, cell_type)?;
38 let tabulation = runtime.basis.tabulate(&runtime.quadrature.points)?;
39 Ok(ReferenceElementEvaluation {
40 basis: runtime.basis,
41 quadrature: runtime.quadrature,
42 tabulation,
43 })
44}
45
46pub fn integrate_reference_scalar<F>(
48 evaluation: &ReferenceElementEvaluation,
49 f: F,
50) -> Result<f64, MeshSieveError>
51where
52 F: Fn(&[f64]) -> f64,
53{
54 if evaluation.quadrature.points.len() != evaluation.quadrature.weights.len() {
55 return Err(MeshSieveError::InvalidGeometry(
56 "quadrature points/weights length mismatch".to_string(),
57 ));
58 }
59 let mut total = 0.0;
60 for (point, weight) in evaluation
61 .quadrature
62 .points
63 .iter()
64 .zip(evaluation.quadrature.weights.iter())
65 {
66 total += weight * f(point);
67 }
68 Ok(total)
69}
70
71#[derive(Clone, Debug)]
73pub struct ElementMatrices {
74 pub stiffness: Vec<f64>,
76 pub load: Vec<f64>,
78}
79
80pub fn assemble_element_matrices<S, F>(
82 coordinates: &Coordinates<f64, S>,
83 cell_type: CellType,
84 cell_nodes: &[PointId],
85 metadata: &DiscretizationMetadata,
86 rhs: F,
87) -> Result<ElementMatrices, MeshSieveError>
88where
89 S: Storage<f64>,
90 F: Fn(&[f64]) -> f64,
91{
92 let node_coords = gather_node_coordinates(coordinates, cell_nodes)?;
93 let runtime = runtime_from_metadata(metadata, cell_type)?;
94 let tabulation = tabulate_element(&runtime, &node_coords)?;
95 Ok(ElementMatrices {
96 stiffness: local_stiffness_matrix(&tabulation),
97 load: local_load_vector(&tabulation, rhs),
98 })
99}
100
101fn gather_node_coordinates<S: Storage<f64>>(
102 coordinates: &Coordinates<f64, S>,
103 cell_nodes: &[PointId],
104) -> Result<Vec<Vec<f64>>, MeshSieveError> {
105 let mut node_coords = Vec::with_capacity(cell_nodes.len());
106 for node in cell_nodes {
107 let slice = coordinates.section().try_restrict(*node)?;
108 node_coords.push(slice.to_vec());
109 }
110 Ok(node_coords)
111}
112
113fn closure_vertex_coordinates<T, S>(
114 topology: &T,
115 coordinates: &Coordinates<f64, S>,
116 cell: PointId,
117 topology_version: u64,
118 order: &ClosureOrder,
119) -> Result<Vec<Vec<f64>>, MeshSieveError>
120where
121 T: Sieve<Point = PointId>,
122 S: Storage<f64>,
123{
124 let index = build_closure_index_unoriented(
125 topology,
126 coordinates.section(),
127 cell,
128 topology_version,
129 order,
130 &IdentitySectionSym,
131 )?;
132 let mut cell_nodes = Vec::new();
133 for point in index.point_order() {
134 if topology.cone_points(point).next().is_none() {
135 cell_nodes.push(point);
136 }
137 }
138 gather_node_coordinates(coordinates, &cell_nodes)
139}
140
141pub fn assemble_element_matrices_from_closure<T, S, F>(
147 topology: &T,
148 coordinates: &Coordinates<f64, S>,
149 cell_type: CellType,
150 cell: PointId,
151 topology_version: u64,
152 order: &ClosureOrder,
153 metadata: &DiscretizationMetadata,
154 rhs: F,
155) -> Result<ElementMatrices, MeshSieveError>
156where
157 T: Sieve<Point = PointId>,
158 S: Storage<f64>,
159 F: Fn(&[f64]) -> f64,
160{
161 let runtime = runtime_from_metadata(metadata, cell_type)?;
162 let node_coords = if let Some(high_order) = coordinates.high_order() {
163 if high_order.section().atlas().contains(cell) {
164 let values = high_order.section().try_restrict(cell)?;
165 let dim = high_order.dimension();
166 let geometry_nodes = values.len() / dim;
167 let geometry_order = metadata
168 .basis_order
169 .unwrap_or(runtime.basis.degree())
170 .max(1);
171 ensure_geometry_order_supported(cell_type, geometry_order)?;
172 if geometry_nodes == runtime.basis.num_nodes() {
173 values.chunks(dim).map(|tuple| tuple.to_vec()).collect()
174 } else {
175 return Err(MeshSieveError::InvalidGeometry(format!(
176 "high-order coordinates for {cell:?} provide {geometry_nodes} nodes, but {:?} P{} requires {}",
177 cell_type,
178 runtime.basis.degree(),
179 runtime.basis.num_nodes()
180 )));
181 }
182 } else {
183 closure_vertex_coordinates(topology, coordinates, cell, topology_version, order)?
184 }
185 } else {
186 closure_vertex_coordinates(topology, coordinates, cell, topology_version, order)?
187 };
188 let tabulation = tabulate_element(&runtime, &node_coords)?;
189 Ok(ElementMatrices {
190 stiffness: local_stiffness_matrix(&tabulation),
191 load: local_load_vector(&tabulation, rhs),
192 })
193}
194
195#[derive(Clone, Debug)]
197pub struct ElementClosureData<V> {
198 pub cell: PointId,
200 pub values: Vec<V>,
202 pub global_indices: Option<Vec<u64>>,
208 pub points: Vec<ElementClosurePoint>,
210}
211
212#[derive(Clone, Debug, PartialEq, Eq)]
214pub struct ElementClosurePoint {
215 pub point: PointId,
217 pub local_dofs: Vec<usize>,
219}
220
221pub fn extract_element_closure<T, V, Sct>(
223 topology: &T,
224 section: &Section<V, Sct>,
225 cell: PointId,
226 topology_version: u64,
227 order: &ClosureOrder,
228) -> Result<ElementClosureData<V>, MeshSieveError>
229where
230 T: Sieve<Point = PointId>,
231 V: Clone,
232 Sct: Storage<V>,
233{
234 let index = build_closure_index_unoriented(
235 topology,
236 section,
237 cell,
238 topology_version,
239 order,
240 &IdentitySectionSym,
241 )?;
242 let values = get_closure(section, &index)?;
243 let points = index
244 .points
245 .iter()
246 .map(|entry| ElementClosurePoint {
247 point: entry.point,
248 local_dofs: entry.permutation.clone(),
249 })
250 .collect();
251 Ok(ElementClosureData {
252 cell,
253 values,
254 global_indices: None,
255 points,
256 })
257}
258
259pub fn extract_oriented_element_closure<T, V, Sct, O, Sym>(
261 topology: &T,
262 section: &Section<V, Sct>,
263 global_map: Option<&LocalToGlobalMap>,
264 cell: PointId,
265 topology_version: u64,
266 order: &ClosureOrder,
267 sym: &Sym,
268) -> Result<ElementClosureData<V>, MeshSieveError>
269where
270 T: OrientedSieve<Point = PointId, Orient = O>,
271 V: Clone,
272 Sct: Storage<V>,
273 O: Orientation + Eq + std::hash::Hash,
274 Sym: SectionSym<O>,
275{
276 let index = build_closure_index(topology, section, cell, topology_version, order, sym)?;
277 let values = get_closure(section, &index)?;
278 let global_indices = if let Some(map) = global_map {
279 let mut indices = Vec::with_capacity(index.len);
280 for entry in &index.points {
281 for &local_dof in &entry.permutation {
282 match map.global_index(entry.point, local_dof) {
283 Ok(global) => indices.push(global),
284 Err(MeshSieveError::ConstraintIndexOutOfBounds { .. }) => {
285 indices.push(u64::MAX)
286 }
287 Err(err) => return Err(err),
288 }
289 }
290 }
291 Some(indices)
292 } else {
293 None
294 };
295 let points = index
296 .points
297 .iter()
298 .map(|entry| ElementClosurePoint {
299 point: entry.point,
300 local_dofs: entry.permutation.clone(),
301 })
302 .collect();
303 Ok(ElementClosureData {
304 cell,
305 values,
306 global_indices,
307 points,
308 })
309}
310
311pub fn insert_element_residual_with_hanging_constraints<V>(
317 closure: &ElementClosureData<V>,
318 element_residual: &[V],
319 global_map: &LocalToGlobalMap,
320 constraints: &crate::data::hanging_node_constraints::HangingNodeConstraints<V>,
321 global_residual: &mut [V],
322) -> Result<(), MeshSieveError>
323where
324 V: Clone + Default + core::ops::AddAssign + core::ops::Mul<Output = V>,
325{
326 if closure.values.len() != element_residual.len() {
327 return Err(MeshSieveError::InvalidGeometry(format!(
328 "element residual length {} does not match closure length {} for {:?}",
329 element_residual.len(),
330 closure.values.len(),
331 closure.cell
332 )));
333 }
334
335 let Some(global_indices) = &closure.global_indices else {
336 return Err(MeshSieveError::InvalidGeometry(
337 "global indices are required for constrained residual insertion".to_string(),
338 ));
339 };
340 if global_indices.len() != element_residual.len() {
341 return Err(MeshSieveError::InvalidGeometry(format!(
342 "global-index length {} does not match residual length {} for {:?}",
343 global_indices.len(),
344 element_residual.len(),
345 closure.cell
346 )));
347 }
348
349 let mut cursor = 0usize;
350 for entry in &closure.points {
351 for &local_dof in &entry.local_dofs {
352 let value = element_residual[cursor].clone();
353 if let Some(point_constraints) = constraints.constraints_for(entry.point)
354 && let Some(constraint) = point_constraints.iter().find(|c| c.index == local_dof)
355 {
356 for term in &constraint.terms {
357 let global = global_map.global_index(term.point, term.index)? as usize;
358 let len = global_residual.len();
359 let slot = global_residual.get_mut(global).ok_or_else(|| {
360 MeshSieveError::InvalidGeometry(format!(
361 "global residual index {global} out of bounds (len={len})"
362 ))
363 })?;
364 *slot += value.clone() * term.weight.clone();
365 }
366 } else {
367 let global = global_indices[cursor] as usize;
368 let len = global_residual.len();
369 let slot = global_residual.get_mut(global).ok_or_else(|| {
370 MeshSieveError::InvalidGeometry(format!(
371 "global residual index {global} out of bounds (len={len})"
372 ))
373 })?;
374 *slot += value;
375 }
376 cursor += 1;
377 }
378 }
379 Ok(())
380}