use crate::mesh::{QuadMeshView2d, QuadratureRule};
use crate::physics::solenoid_stress::axisym::{accumulate_stiffness, build_b_matrix};
use crate::physics::solenoid_stress::family::QuadElementFamily;
use crate::physics::solenoid_stress::geometry::validate_axisymmetric_mesh;
use crate::physics::solenoid_stress::types::{
DOF_PER_NODE, Real, StiffnessTriplets, local_dofs, two_pi,
};
pub(crate) fn assemble_stiffness_for_family<
F: Real,
Family,
const NODES_PER_ELEMENT: usize,
const DOF_PER_ELEMENT: usize,
>(
mesh: QuadMeshView2d<'_, F, NODES_PER_ELEMENT>,
material_ids: &[usize],
material_table: &[[[F; 4]; 4]],
quadrature: QuadratureRule,
) -> Result<StiffnessTriplets<F>, String>
where
Family: QuadElementFamily<NODES_PER_ELEMENT>,
{
const {
assert!(DOF_PER_ELEMENT == DOF_PER_NODE * NODES_PER_ELEMENT);
}
validate_axisymmetric_mesh(mesh)?;
if material_ids.len() != mesh.num_elements() {
return Err(format!(
"material_ids has length {}, but mesh has {} elements",
material_ids.len(),
mesh.num_elements()
));
}
let mut rows = Vec::with_capacity(mesh.num_elements() * DOF_PER_ELEMENT * DOF_PER_ELEMENT);
let mut cols = Vec::with_capacity(mesh.num_elements() * DOF_PER_ELEMENT * DOF_PER_ELEMENT);
let mut vals = Vec::with_capacity(mesh.num_elements() * DOF_PER_ELEMENT * DOF_PER_ELEMENT);
let two_pi = two_pi::<F>();
for element_index in 0..mesh.num_elements() {
let coords = mesh.element_coords(element_index)?;
let nodes = mesh.element_nodes(element_index)?;
let material_id = material_ids[element_index];
let material = material_table.get(material_id).ok_or_else(|| {
format!("material_id {material_id} on element {element_index} is out of range")
})?;
let mut ke = [[F::zero(); DOF_PER_ELEMENT]; DOF_PER_ELEMENT];
for sample in Family::volume_samples::<F>(&coords, quadrature)? {
let b = build_b_matrix::<F, NODES_PER_ELEMENT, DOF_PER_ELEMENT>(
&sample.n,
&sample.grad_phys,
sample.point[0],
)?;
let scale = two_pi * sample.point[0] * sample.det_j * sample.weight;
accumulate_stiffness(&mut ke, material, &b, scale);
}
let global_dofs = local_dofs::<NODES_PER_ELEMENT, DOF_PER_ELEMENT>(&nodes);
for row in 0..DOF_PER_ELEMENT {
for col in 0..DOF_PER_ELEMENT {
rows.push(global_dofs[row]);
cols.push(global_dofs[col]);
vals.push(ke[row][col]);
}
}
}
Ok(StiffnessTriplets { rows, cols, vals })
}
#[cfg(test)]
mod tests {
use super::assemble_stiffness_for_family;
use crate::mesh::QuadratureRule;
use crate::physics::solenoid_stress::convenience::isotropic_axisymmetric_material;
use crate::physics::solenoid_stress::family::Quad4Family;
use crate::physics::solenoid_stress::test_utils::single_element_quad4_mesh;
use crate::physics::solenoid_stress::types::dof_per_element;
#[test]
fn single_element_has_symmetric_stiffness() {
let mesh = single_element_quad4_mesh();
let material_ids = [0usize];
let material_table = [isotropic_axisymmetric_material(200.0e9, 0.27)];
let result = assemble_stiffness_for_family::<f64, Quad4Family, 4, { dof_per_element(4) }>(
mesh,
&material_ids,
&material_table,
QuadratureRule::GaussLegendre3,
)
.expect("assembly should succeed");
let mut dense = [[0.0; 8]; 8];
for ((row, col), val) in result.rows.iter().zip(&result.cols).zip(&result.vals) {
dense[*row][*col] += *val;
}
for row in 0..8 {
for col in 0..8 {
let scale = dense[row][col].abs().max(dense[col][row].abs()).max(1.0);
assert!((dense[row][col] - dense[col][row]).abs() / scale < 1.0e-12);
}
}
}
}