use crate::data::closure::{
ClosureOrder, IdentitySectionSym, SectionSym, build_closure_index,
build_closure_index_unoriented, get_closure,
};
use crate::data::coordinates::Coordinates;
use crate::data::discretization::DiscretizationMetadata;
use crate::data::global_map::LocalToGlobalMap;
use crate::data::section::Section;
use crate::data::storage::Storage;
use crate::discretization::runtime::{
Basis, BasisTabulation, QuadratureRule, ensure_geometry_order_supported, local_load_vector,
local_stiffness_matrix, runtime_from_metadata, tabulate_element,
};
use crate::mesh_error::MeshSieveError;
use crate::topology::cell_type::CellType;
use crate::topology::point::PointId;
use crate::topology::sieve::{Orientation, OrientedSieve, Sieve};
#[derive(Clone, Debug)]
pub struct ReferenceElementEvaluation {
pub basis: Basis,
pub quadrature: QuadratureRule,
pub tabulation: BasisTabulation,
}
pub fn evaluate_reference_element(
cell_type: CellType,
metadata: &DiscretizationMetadata,
) -> Result<ReferenceElementEvaluation, MeshSieveError> {
let runtime = runtime_from_metadata(metadata, cell_type)?;
let tabulation = runtime.basis.tabulate(&runtime.quadrature.points)?;
Ok(ReferenceElementEvaluation {
basis: runtime.basis,
quadrature: runtime.quadrature,
tabulation,
})
}
pub fn integrate_reference_scalar<F>(
evaluation: &ReferenceElementEvaluation,
f: F,
) -> Result<f64, MeshSieveError>
where
F: Fn(&[f64]) -> f64,
{
if evaluation.quadrature.points.len() != evaluation.quadrature.weights.len() {
return Err(MeshSieveError::InvalidGeometry(
"quadrature points/weights length mismatch".to_string(),
));
}
let mut total = 0.0;
for (point, weight) in evaluation
.quadrature
.points
.iter()
.zip(evaluation.quadrature.weights.iter())
{
total += weight * f(point);
}
Ok(total)
}
#[derive(Clone, Debug)]
pub struct ElementMatrices {
pub stiffness: Vec<f64>,
pub load: Vec<f64>,
}
pub fn assemble_element_matrices<S, F>(
coordinates: &Coordinates<f64, S>,
cell_type: CellType,
cell_nodes: &[PointId],
metadata: &DiscretizationMetadata,
rhs: F,
) -> Result<ElementMatrices, MeshSieveError>
where
S: Storage<f64>,
F: Fn(&[f64]) -> f64,
{
let node_coords = gather_node_coordinates(coordinates, cell_nodes)?;
let runtime = runtime_from_metadata(metadata, cell_type)?;
let tabulation = tabulate_element(&runtime, &node_coords)?;
Ok(ElementMatrices {
stiffness: local_stiffness_matrix(&tabulation),
load: local_load_vector(&tabulation, rhs),
})
}
fn gather_node_coordinates<S: Storage<f64>>(
coordinates: &Coordinates<f64, S>,
cell_nodes: &[PointId],
) -> Result<Vec<Vec<f64>>, MeshSieveError> {
let mut node_coords = Vec::with_capacity(cell_nodes.len());
for node in cell_nodes {
let slice = coordinates.section().try_restrict(*node)?;
node_coords.push(slice.to_vec());
}
Ok(node_coords)
}
fn closure_vertex_coordinates<T, S>(
topology: &T,
coordinates: &Coordinates<f64, S>,
cell: PointId,
topology_version: u64,
order: &ClosureOrder,
) -> Result<Vec<Vec<f64>>, MeshSieveError>
where
T: Sieve<Point = PointId>,
S: Storage<f64>,
{
let index = build_closure_index_unoriented(
topology,
coordinates.section(),
cell,
topology_version,
order,
&IdentitySectionSym,
)?;
let mut cell_nodes = Vec::new();
for point in index.point_order() {
if topology.cone_points(point).next().is_none() {
cell_nodes.push(point);
}
}
gather_node_coordinates(coordinates, &cell_nodes)
}
pub fn assemble_element_matrices_from_closure<T, S, F>(
topology: &T,
coordinates: &Coordinates<f64, S>,
cell_type: CellType,
cell: PointId,
topology_version: u64,
order: &ClosureOrder,
metadata: &DiscretizationMetadata,
rhs: F,
) -> Result<ElementMatrices, MeshSieveError>
where
T: Sieve<Point = PointId>,
S: Storage<f64>,
F: Fn(&[f64]) -> f64,
{
let runtime = runtime_from_metadata(metadata, cell_type)?;
let node_coords = if let Some(high_order) = coordinates.high_order() {
if high_order.section().atlas().contains(cell) {
let values = high_order.section().try_restrict(cell)?;
let dim = high_order.dimension();
let geometry_nodes = values.len() / dim;
let geometry_order = metadata
.basis_order
.unwrap_or(runtime.basis.degree())
.max(1);
ensure_geometry_order_supported(cell_type, geometry_order)?;
if geometry_nodes == runtime.basis.num_nodes() {
values.chunks(dim).map(|tuple| tuple.to_vec()).collect()
} else {
return Err(MeshSieveError::InvalidGeometry(format!(
"high-order coordinates for {cell:?} provide {geometry_nodes} nodes, but {:?} P{} requires {}",
cell_type,
runtime.basis.degree(),
runtime.basis.num_nodes()
)));
}
} else {
closure_vertex_coordinates(topology, coordinates, cell, topology_version, order)?
}
} else {
closure_vertex_coordinates(topology, coordinates, cell, topology_version, order)?
};
let tabulation = tabulate_element(&runtime, &node_coords)?;
Ok(ElementMatrices {
stiffness: local_stiffness_matrix(&tabulation),
load: local_load_vector(&tabulation, rhs),
})
}
#[derive(Clone, Debug)]
pub struct ElementClosureData<V> {
pub cell: PointId,
pub values: Vec<V>,
pub global_indices: Option<Vec<u64>>,
pub points: Vec<ElementClosurePoint>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ElementClosurePoint {
pub point: PointId,
pub local_dofs: Vec<usize>,
}
pub fn extract_element_closure<T, V, Sct>(
topology: &T,
section: &Section<V, Sct>,
cell: PointId,
topology_version: u64,
order: &ClosureOrder,
) -> Result<ElementClosureData<V>, MeshSieveError>
where
T: Sieve<Point = PointId>,
V: Clone,
Sct: Storage<V>,
{
let index = build_closure_index_unoriented(
topology,
section,
cell,
topology_version,
order,
&IdentitySectionSym,
)?;
let values = get_closure(section, &index)?;
let points = index
.points
.iter()
.map(|entry| ElementClosurePoint {
point: entry.point,
local_dofs: entry.permutation.clone(),
})
.collect();
Ok(ElementClosureData {
cell,
values,
global_indices: None,
points,
})
}
pub fn extract_oriented_element_closure<T, V, Sct, O, Sym>(
topology: &T,
section: &Section<V, Sct>,
global_map: Option<&LocalToGlobalMap>,
cell: PointId,
topology_version: u64,
order: &ClosureOrder,
sym: &Sym,
) -> Result<ElementClosureData<V>, MeshSieveError>
where
T: OrientedSieve<Point = PointId, Orient = O>,
V: Clone,
Sct: Storage<V>,
O: Orientation + Eq + std::hash::Hash,
Sym: SectionSym<O>,
{
let index = build_closure_index(topology, section, cell, topology_version, order, sym)?;
let values = get_closure(section, &index)?;
let global_indices = if let Some(map) = global_map {
let mut indices = Vec::with_capacity(index.len);
for entry in &index.points {
for &local_dof in &entry.permutation {
match map.global_index(entry.point, local_dof) {
Ok(global) => indices.push(global),
Err(MeshSieveError::ConstraintIndexOutOfBounds { .. }) => {
indices.push(u64::MAX)
}
Err(err) => return Err(err),
}
}
}
Some(indices)
} else {
None
};
let points = index
.points
.iter()
.map(|entry| ElementClosurePoint {
point: entry.point,
local_dofs: entry.permutation.clone(),
})
.collect();
Ok(ElementClosureData {
cell,
values,
global_indices,
points,
})
}
pub fn insert_element_residual_with_hanging_constraints<V>(
closure: &ElementClosureData<V>,
element_residual: &[V],
global_map: &LocalToGlobalMap,
constraints: &crate::data::hanging_node_constraints::HangingNodeConstraints<V>,
global_residual: &mut [V],
) -> Result<(), MeshSieveError>
where
V: Clone + Default + core::ops::AddAssign + core::ops::Mul<Output = V>,
{
if closure.values.len() != element_residual.len() {
return Err(MeshSieveError::InvalidGeometry(format!(
"element residual length {} does not match closure length {} for {:?}",
element_residual.len(),
closure.values.len(),
closure.cell
)));
}
let Some(global_indices) = &closure.global_indices else {
return Err(MeshSieveError::InvalidGeometry(
"global indices are required for constrained residual insertion".to_string(),
));
};
if global_indices.len() != element_residual.len() {
return Err(MeshSieveError::InvalidGeometry(format!(
"global-index length {} does not match residual length {} for {:?}",
global_indices.len(),
element_residual.len(),
closure.cell
)));
}
let mut cursor = 0usize;
for entry in &closure.points {
for &local_dof in &entry.local_dofs {
let value = element_residual[cursor].clone();
if let Some(point_constraints) = constraints.constraints_for(entry.point)
&& let Some(constraint) = point_constraints.iter().find(|c| c.index == local_dof)
{
for term in &constraint.terms {
let global = global_map.global_index(term.point, term.index)? as usize;
let len = global_residual.len();
let slot = global_residual.get_mut(global).ok_or_else(|| {
MeshSieveError::InvalidGeometry(format!(
"global residual index {global} out of bounds (len={len})"
))
})?;
*slot += value.clone() * term.weight.clone();
}
} else {
let global = global_indices[cursor] as usize;
let len = global_residual.len();
let slot = global_residual.get_mut(global).ok_or_else(|| {
MeshSieveError::InvalidGeometry(format!(
"global residual index {global} out of bounds (len={len})"
))
})?;
*slot += value;
}
cursor += 1;
}
}
Ok(())
}