forked from 1b-agent/fieldcraft-upstream
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
61 lines
2.5 KiB
Rust
61 lines
2.5 KiB
Rust
//! Relaxation fields: what a node relaxation (`mesh.node.relax`) did, kept so it can be done again
|
|
//! on a new mesh of the same geometry.
|
|
//!
|
|
//! A field is the relaxed region of the mesh it was made on: each element there as its corners
|
|
//! were (`at`) and where they went (`moved`). A node of a later mesh that lies in one of these
|
|
//! cells takes the displacement interpolated with the cell's own shape functions at its
|
|
//! parametric coordinates (barycentric in a tetrahedron or triangle, trilinear `(u, v, w)` in a
|
|
//! hexahedron, …), so the relaxed shape carries over to a coarser or finer mesh. Fields are
|
|
//! definitions of the model: journalled with the command that made them and saved in the
|
|
//! `.fieldcraft` file.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{ElementKind, Error, Result, check_coordinates};
|
|
|
|
/// Most cells one field holds.
|
|
pub const MAX_RELAXATION_CELLS: usize = 1_000_000;
|
|
|
|
/// One cell of a field: a linear element's corners before and after.
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct RelaxationCell {
|
|
/// tri3, quad4, tet4, wedge6 or hex8 (a quadratic element's corners).
|
|
pub kind: ElementKind,
|
|
pub at: Vec<[f64; 3]>,
|
|
pub moved: Vec<[f64; 3]>,
|
|
}
|
|
|
|
/// A relaxation field.
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct Relaxation {
|
|
/// The bodies it was made on (empty: any).
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
pub bodies: Vec<String>,
|
|
pub cells: Vec<RelaxationCell>,
|
|
/// Applied again by itself after each remesh of its bodies.
|
|
#[serde(default)]
|
|
pub reapply: bool,
|
|
}
|
|
|
|
impl Relaxation {
|
|
pub fn check(&self) -> Result<()> {
|
|
if self.cells.len() > MAX_RELAXATION_CELLS {
|
|
return Err(Error::Limit(format!("a relaxation field holds at most {MAX_RELAXATION_CELLS} cells")));
|
|
}
|
|
for c in &self.cells {
|
|
if !matches!(c.kind, ElementKind::Tri3 | ElementKind::Quad4 | ElementKind::Tet4 | ElementKind::Wedge6 | ElementKind::Hex8) {
|
|
return Err(Error::Invalid(format!("a relaxation cell is linear, not {}", c.kind.name())));
|
|
}
|
|
let n = c.kind.node_count();
|
|
if c.at.len() != n || c.moved.len() != n {
|
|
return Err(Error::Invalid(format!("a {} relaxation cell has {n} corners", c.kind.name())));
|
|
}
|
|
check_coordinates(c.at.as_flattened())?;
|
|
check_coordinates(c.moved.as_flattened())?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|