use crate::mesh::{QuadMeshView2d, QuadratureRule};
use crate::physics::solenoid_stress::family::QuadElementFamily;
use crate::physics::solenoid_stress::geometry::{FaceSample, validate_structural_2d_mesh};
use crate::physics::solenoid_stress::types::{
DOF_PER_NODE, Structural2dFormulation, TractionLoad, local_dofs, scatter_local_matrix,
};
use super::{SparseOperator, collect_sparse_operator_chunks, concat_sparse_operators};
fn traction_face_kernel<const NODES_PER_ELEMENT: usize, const DOF_PER_ELEMENT: usize>(
samples: &[FaceSample<f64, NODES_PER_ELEMENT>],
formulation: Structural2dFormulation,
) -> Result<[[f64; 2]; DOF_PER_ELEMENT], String> {
const {
assert!(DOF_PER_ELEMENT == DOF_PER_NODE * NODES_PER_ELEMENT);
}
let mut local = [[0.0; 2]; DOF_PER_ELEMENT];
for sample in samples {
let tangent_norm =
(sample.tangent[0] * sample.tangent[0] + sample.tangent[1] * sample.tangent[1]).sqrt();
let scale = formulation.face_scale(sample.point, tangent_norm, sample.weight)?;
for local_node in 0..NODES_PER_ELEMENT {
local[2 * local_node][0] += scale * sample.n[local_node];
local[2 * local_node + 1][1] += scale * sample.n[local_node];
}
}
Ok(local)
}
pub(crate) fn traction_operator_for_family<
Family,
const NODES_PER_ELEMENT: usize,
const DOF_PER_ELEMENT: usize,
>(
mesh: QuadMeshView2d<'_, f64, NODES_PER_ELEMENT>,
traction_faces: &[TractionLoad],
formulation: Structural2dFormulation,
quadrature: QuadratureRule,
) -> Result<SparseOperator, String>
where
Family: QuadElementFamily<NODES_PER_ELEMENT>,
{
const {
assert!(DOF_PER_ELEMENT == DOF_PER_NODE * NODES_PER_ELEMENT);
}
validate_structural_2d_mesh(mesh, formulation)?;
traction_operator_range_for_family::<Family, NODES_PER_ELEMENT, DOF_PER_ELEMENT>(
mesh,
traction_faces,
formulation,
quadrature,
0,
traction_faces.len(),
)
}
pub(crate) fn traction_operator_for_family_par<
Family,
const NODES_PER_ELEMENT: usize,
const DOF_PER_ELEMENT: usize,
>(
mesh: QuadMeshView2d<'_, f64, NODES_PER_ELEMENT>,
traction_faces: &[TractionLoad],
formulation: Structural2dFormulation,
quadrature: QuadratureRule,
) -> Result<SparseOperator, String>
where
Family: QuadElementFamily<NODES_PER_ELEMENT>,
{
const {
assert!(DOF_PER_ELEMENT == DOF_PER_NODE * NODES_PER_ELEMENT);
}
validate_structural_2d_mesh(mesh, formulation)?;
let chunks = collect_sparse_operator_chunks(traction_faces.len(), |start, end| {
traction_operator_range_for_family::<Family, NODES_PER_ELEMENT, DOF_PER_ELEMENT>(
mesh,
traction_faces,
formulation,
quadrature,
start,
end,
)
})?;
Ok(concat_sparse_operators(
chunks,
mesh.num_nodes() * 2,
2 * traction_faces.len(),
traction_faces.len() * DOF_PER_ELEMENT * 2,
))
}
pub(crate) fn apply_traction_rhs_for_family<
Family,
const NODES_PER_ELEMENT: usize,
const DOF_PER_ELEMENT: usize,
>(
mesh: QuadMeshView2d<'_, f64, NODES_PER_ELEMENT>,
traction_faces: &[TractionLoad],
formulation: Structural2dFormulation,
quadrature: QuadratureRule,
values: &[f64],
global_to_reduced: &[usize],
rhs: &mut [f64],
) -> Result<(), String>
where
Family: QuadElementFamily<NODES_PER_ELEMENT>,
{
const {
assert!(DOF_PER_ELEMENT == DOF_PER_NODE * NODES_PER_ELEMENT);
}
validate_structural_2d_mesh(mesh, formulation)?;
let expected = 2 * traction_faces.len();
if values.len() != expected {
return Err(format!(
"traction_values has length {}, but operator expects {expected} values",
values.len()
));
}
for_traction_face_blocks::<Family, NODES_PER_ELEMENT, DOF_PER_ELEMENT, _>(
mesh,
traction_faces,
formulation,
quadrature,
0,
traction_faces.len(),
|load_index, global_rows, local| {
let radial = values[2 * load_index];
let axial = values[2 * load_index + 1];
for local_dof in 0..DOF_PER_ELEMENT {
let reduced_row = global_to_reduced[global_rows[local_dof]];
if reduced_row != usize::MAX {
rhs[reduced_row] += local[local_dof][0] * radial + local[local_dof][1] * axial;
}
}
Ok(())
},
)
}
fn for_traction_face_blocks<
Family,
const NODES_PER_ELEMENT: usize,
const DOF_PER_ELEMENT: usize,
Consume,
>(
mesh: QuadMeshView2d<'_, f64, NODES_PER_ELEMENT>,
traction_faces: &[TractionLoad],
formulation: Structural2dFormulation,
quadrature: QuadratureRule,
load_start: usize,
load_end: usize,
mut consume: Consume,
) -> Result<(), String>
where
Family: QuadElementFamily<NODES_PER_ELEMENT>,
Consume:
FnMut(usize, [usize; DOF_PER_ELEMENT], [[f64; 2]; DOF_PER_ELEMENT]) -> Result<(), String>,
{
let nloads = load_end - load_start;
for (load_index, load) in traction_faces
.iter()
.enumerate()
.skip(load_start)
.take(nloads)
{
if load.element >= mesh.num_elements() {
return Err(format!(
"traction load references element {}, but mesh has only {} elements",
load.element,
mesh.num_elements()
));
}
let coords = mesh.element_coords(load.element)?;
let nodes = mesh.element_nodes(load.element)?;
let samples = Family::face_samples(&coords, load.local_face, quadrature)?;
let local =
traction_face_kernel::<NODES_PER_ELEMENT, DOF_PER_ELEMENT>(&samples, formulation)?;
let global_rows = local_dofs::<NODES_PER_ELEMENT, DOF_PER_ELEMENT>(&nodes);
consume(load_index, global_rows, local)?;
}
Ok(())
}
fn traction_operator_range_for_family<
Family,
const NODES_PER_ELEMENT: usize,
const DOF_PER_ELEMENT: usize,
>(
mesh: QuadMeshView2d<'_, f64, NODES_PER_ELEMENT>,
traction_faces: &[TractionLoad],
formulation: Structural2dFormulation,
quadrature: QuadratureRule,
load_start: usize,
load_end: usize,
) -> Result<SparseOperator, String>
where
Family: QuadElementFamily<NODES_PER_ELEMENT>,
{
let ndof = mesh.num_nodes() * 2;
let ncol = 2 * traction_faces.len();
let nloads = load_end - load_start;
let mut rows = Vec::with_capacity(nloads * DOF_PER_ELEMENT * 2);
let mut cols = Vec::with_capacity(nloads * DOF_PER_ELEMENT * 2);
let mut vals = Vec::with_capacity(nloads * DOF_PER_ELEMENT * 2);
for_traction_face_blocks::<Family, NODES_PER_ELEMENT, DOF_PER_ELEMENT, _>(
mesh,
traction_faces,
formulation,
quadrature,
load_start,
load_end,
|load_index, global_rows, local| {
let global_cols = [2 * load_index, 2 * load_index + 1];
scatter_local_matrix(
&mut rows,
&mut cols,
&mut vals,
&global_rows,
&global_cols,
&local,
);
Ok(())
},
)?;
Ok(SparseOperator {
rows,
cols,
vals,
nrow: ndof,
ncol,
})
}