forked from 1b-agent/fieldcraft-upstream
Each secondary node follows the main surface under it as the weighted average u_A = sum_J (M_AJ / sum_K M_AK) u_J with M_AJ = int psi_A N_J over the reference overlaps and psi the secondary faces' dual shape functions (Wohlmuth 2000), so the secondary mortar matrix is diagonal; the rows join the couplings' constraint elimination in every implicit analysis. Uncovered secondary nodes stay free with a warning; heat transfer and explicit dynamics refuse ties by name. Tested by the tie patch test on non-matching hex8/tet4 meshes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
115 lines
4.7 KiB
Rust
115 lines
4.7 KiB
Rust
//! FieldCraft's implicit solver: linear static and frequency analysis of a model's steps.
|
||
//!
|
||
//! The pipeline, for one step of a [`fieldcraft_model::Model`]:
|
||
//! 1. **System** ([`system`]): the model is resolved; every section's elements become solid
|
||
//! elements with their material's elasticity and the section's formulation
|
||
//! ([`fieldcraft_fem`] kernels); the step's boundary conditions give the prescribed degrees of
|
||
//! freedom, its loads the load vector (forces and tractions as the model distributes them,
|
||
//! consistent pressure loads, gravity and body forces).
|
||
//! 2. **Assembly** ([`sparse`]) on the node graph, elements in label order.
|
||
//! 3. **Ordering** ([`ordering`]): nested dissection of the nodes; the free degrees of freedom
|
||
//! are numbered in that order and the prescribed ones eliminated (u_p moved to the right-hand
|
||
//! side), with no penalty numbers.
|
||
//! 4. **Solution** ([`cholesky`]): sparse Cholesky; reactions K u − f at the prescribed degrees
|
||
//! of freedom; stresses at the integration points, extrapolated and averaged at the nodes, and
|
||
//! von Mises. Frequency analysis ([`eigen`]) runs shift-invert block Lanczos on the same
|
||
//! kind of factorization. Indefinite matrices (tangents past a limit point, strained mixed
|
||
//! elements, shifted operators) are factored by the symmetric indefinite LDLᵀ ([`ldlt`]) on
|
||
//! the same symbolic factorization, which also counts their negative eigenvalues.
|
||
//!
|
||
//! Element matrices and stresses, the factorization's subtrees and dense tiles, the Lanczos
|
||
//! block solves and matrix products run on threads ([`fieldcraft_parallel`]), but every number
|
||
//! is computed by one task in an order fixed by the model alone and sums are taken in a fixed
|
||
//! order, so the same model gives the same numbers bit for bit on any number of threads and on
|
||
//! every platform; sizes and iterations are capped by [`Limits`].
|
||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||
#![forbid(unsafe_code)]
|
||
// Matrix kernels read as the textbook formulas with index loops.
|
||
#![allow(clippy::needless_range_loop)]
|
||
|
||
pub mod analysis;
|
||
pub mod buckle;
|
||
pub mod cholesky;
|
||
pub mod constraints;
|
||
pub mod contact;
|
||
pub mod discrete;
|
||
pub mod eigen;
|
||
pub mod heat;
|
||
pub mod ldlt;
|
||
pub mod nonlinear;
|
||
pub mod ordering;
|
||
pub mod sparse;
|
||
pub mod system;
|
||
pub mod tie;
|
||
|
||
pub use analysis::{
|
||
ElementStress, FrequencyOptions, FrequencyResult, Location, MassKind, Mode, PointResult, StaticResult, Stats, StepResult, solve_frequency,
|
||
solve_static,
|
||
};
|
||
pub use buckle::{Buckling, solve_buckle};
|
||
pub use heat::{HeatResult, solve_heat};
|
||
pub use nonlinear::solve_nonlinear;
|
||
|
||
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
|
||
pub enum Error {
|
||
#[error(transparent)]
|
||
Model(#[from] fieldcraft_model::Error),
|
||
#[error("invalid input: {0}")]
|
||
Invalid(String),
|
||
#[error("not supported yet: {0}")]
|
||
NotSupported(String),
|
||
/// An element kernel failed (inverted geometry, a formulation it lacks, …).
|
||
#[error("element {label}: {source}")]
|
||
Element { label: u32, source: fieldcraft_fem::Error },
|
||
/// The stiffness (or shifted matrix) is singular.
|
||
#[error("{0}")]
|
||
Singular(String),
|
||
#[error("limit reached: {0}")]
|
||
Limit(String),
|
||
#[error("did not converge: {0}")]
|
||
NotConverged(String),
|
||
/// A broken internal invariant (a bug), reported instead of a panic.
|
||
#[error("internal error: {0}")]
|
||
Internal(String),
|
||
}
|
||
|
||
pub type Result<T> = std::result::Result<T, Error>;
|
||
|
||
impl Error {
|
||
/// A kernel error, naming the element.
|
||
pub fn element(label: u32, source: fieldcraft_fem::Error) -> Error {
|
||
Error::Element { label, source }
|
||
}
|
||
}
|
||
|
||
/// Caps on the size of a solution.
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
pub struct Limits {
|
||
/// Degrees of freedom of the mesh.
|
||
pub max_dofs: usize,
|
||
/// Entries of the Cholesky factor (8 bytes each); also bounds the Lanczos basis memory.
|
||
pub max_factor_entries: usize,
|
||
/// Lanczos vectors (iterations × block size).
|
||
pub max_lanczos_vectors: usize,
|
||
/// Newton iterations of a nonlinear step, over all its increments.
|
||
pub max_newton_iterations: usize,
|
||
}
|
||
|
||
impl Limits {
|
||
pub const MAX_DOFS: usize = 6_000_000;
|
||
pub const MAX_FACTOR_ENTRIES: usize = 400_000_000;
|
||
pub const MAX_LANCZOS_VECTORS: usize = 3_000;
|
||
pub const MAX_NEWTON_ITERATIONS: usize = 100_000;
|
||
}
|
||
|
||
impl Default for Limits {
|
||
fn default() -> Limits {
|
||
Limits {
|
||
max_dofs: Limits::MAX_DOFS,
|
||
max_factor_entries: Limits::MAX_FACTOR_ENTRIES,
|
||
max_lanczos_vectors: Limits::MAX_LANCZOS_VECTORS,
|
||
max_newton_iterations: Limits::MAX_NEWTON_ITERATIONS,
|
||
}
|
||
}
|
||
}
|