forked from 1b-agent/fieldcraft-upstream
Rename the project to FieldCraft
Crates fieldcraft-*, binaries fieldcraft and fieldcraft-cli, the .fieldcraft file (format tag fieldcraft/1, manifest fieldcraft.json), FIELDCRAFT_* environment variables, the MCP server name, egui widget ids (fc_*), the app icon document FieldCraft.icon and its generated family, docs and licences. No compatibility shim: no files of the earlier name exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
1 parent
9bc4e1b182
commit
d5bd74570f
268 files changed
+1666
-1666
No files matched your search
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "modelcraft-explicit"
|
||||
description = "ModelCraft's explicit dynamics solver: central-difference time integration with lumped mass, one-point hexahedra with hourglass control, nodal-pressure tetrahedra, bulk viscosity and energy accounting"
|
||||
name = "fieldcraft-explicit"
|
||||
description = "FieldCraft's explicit dynamics solver: central-difference time integration with lumped mass, one-point hexahedra with hourglass control, nodal-pressure tetrahedra, bulk viscosity and energy accounting"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@@ -9,8 +9,8 @@ license.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
modelcraft-model = { workspace = true }
|
||||
modelcraft-fem = { workspace = true }
|
||||
modelcraft-parallel = { workspace = true }
|
||||
fieldcraft-model = { workspace = true }
|
||||
fieldcraft-fem = { workspace = true }
|
||||
fieldcraft-parallel = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
@@ -3,7 +3,7 @@
|
||||
//! - **Trusses** (rod2, rod3), elastic, with lumped (HRZ) mass. With nonlinear geometry a rod2
|
||||
//! is corotational: its strain is (l − L)/L from its current length l and its force acts along
|
||||
//! its current axis (Belytschko et al. 2014, §2.4 and §4.5); a rod3 then is not supported yet.
|
||||
//! Without it the internal force is K u with the kernel's stiffness ([`modelcraft_fem::truss`]).
|
||||
//! Without it the internal force is K u with the kernel's stiffness ([`fieldcraft_fem::truss`]).
|
||||
//! The stress is the uniaxial tensor along the axis (the mean of the integration points'), and
|
||||
//! the critical increment comes from the kernel's frequency bound (exact for rod2: L/c).
|
||||
//! - **Point masses** add to the lumped mass of their node, per direction.
|
||||
@@ -21,10 +21,10 @@
|
||||
//! §6.3), which is 2/D for a dashpot alone. Rotary inertia and rotational springs need
|
||||
//! rotational degrees of freedom, which this solver does not have yet.
|
||||
|
||||
use modelcraft_fem::DofKind;
|
||||
use modelcraft_fem::dense::Matrix;
|
||||
use modelcraft_fem::truss::{Truss, uniaxial};
|
||||
use modelcraft_model::{ElementKind, Resolved, ResolvedDirection};
|
||||
use fieldcraft_fem::DofKind;
|
||||
use fieldcraft_fem::dense::Matrix;
|
||||
use fieldcraft_fem::truss::{Truss, uniaxial};
|
||||
use fieldcraft_model::{ElementKind, Resolved, ResolvedDirection};
|
||||
|
||||
use crate::system::NODE_DOFS;
|
||||
use crate::{Error, Result};
|
||||
@@ -92,7 +92,7 @@ impl Rod {
|
||||
|
||||
/// The element's degrees of freedom, node by node.
|
||||
pub fn dofs(&self) -> impl Iterator<Item = usize> + '_ {
|
||||
self.nodes.iter().flat_map(|n| modelcraft_fem::truss::DOFS.node.iter().filter_map(DofKind::direction).map(|d| dof(*n, d)))
|
||||
self.nodes.iter().flat_map(|n| fieldcraft_fem::truss::DOFS.node.iter().filter_map(DofKind::direction).map(|d| dof(*n, d)))
|
||||
}
|
||||
|
||||
/// Add its internal force into `force` and return the element's mean stress and strain
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
//! strain and the stiffness-form hourglass forces) is rotated with the increment's rotation —
|
||||
//! the Cayley transform of the spin increment (Jaumann rate, Hughes & Winget 1980) or the
|
||||
//! increment of the polar rotation (Green–Naghdi rate, D. P. Flanagan, L. M. Taylor, CMAME
|
||||
//! 62(3):305–320, 1987), see [`modelcraft_fem::rotation`] — then the constitutive increment is
|
||||
//! 62(3):305–320, 1987), see [`fieldcraft_fem::rotation`] — then the constitutive increment is
|
||||
//! added: hypoelastic, or J2 radial return with Johnson–Cook damage
|
||||
//! ([`modelcraft_fem::plasticity`]). Orthotropic elasticity is updated in the polar frame (its
|
||||
//! ([`fieldcraft_fem::plasticity`]). Orthotropic elasticity is updated in the polar frame (its
|
||||
//! axes turn with the material) whichever rate the step names.
|
||||
//!
|
||||
//! **One-point hexahedron** (D. P. Flanagan, T. Belytschko, IJNME 17(5):679–706, 1981): the
|
||||
@@ -44,7 +44,7 @@
|
||||
//! Δt = L / (Q + √(Q² + c²)), Q = C_l c + C_q L |ε̇ᵥ| (Wilkins 1980).
|
||||
//!
|
||||
//! **Critical increment.** Each element's stable length L = 2c/ω_max comes from the bound on its
|
||||
//! highest frequency in its current shape ([`modelcraft_fem::uniform`], with the element's lumped
|
||||
//! highest frequency in its current shape ([`fieldcraft_fem::uniform`], with the element's lumped
|
||||
//! masses, plus the hourglass stiffness when it is used), so without bulk viscosity its critical
|
||||
//! increment is 2/ω_max (Belytschko et al. 2014, §6.3). The elastic moduli bound the tangent
|
||||
//! moduli of plastic flow, so the bound holds for the inelastic materials too.
|
||||
@@ -54,11 +54,11 @@
|
||||
//! reaches its material's erosion plastic strain is removed: it carries no stress from then on,
|
||||
//! its nodes keep their mass and momentum, and its internal energy at that moment is reported.
|
||||
|
||||
use modelcraft_fem::material::stress;
|
||||
use modelcraft_fem::plasticity::{Increment, PointState};
|
||||
use modelcraft_fem::rotation::{self, IDENTITY, Mat3, Polar};
|
||||
use modelcraft_fem::uniform::{Hex8, Tet4, UniformStrain};
|
||||
use modelcraft_model::{BulkViscosity, HourglassKind, StressRate};
|
||||
use fieldcraft_fem::material::stress;
|
||||
use fieldcraft_fem::plasticity::{Increment, PointState};
|
||||
use fieldcraft_fem::rotation::{self, IDENTITY, Mat3, Polar};
|
||||
use fieldcraft_fem::uniform::{Hex8, Tet4, UniformStrain};
|
||||
use fieldcraft_model::{BulkViscosity, HourglassKind, StressRate};
|
||||
|
||||
use crate::system::{Material, NODE_DOFS, System};
|
||||
use crate::{Error, Result};
|
||||
@@ -330,7 +330,7 @@ fn current(mat: &Material, rest: f64, volume: f64) -> (f64, f64) {
|
||||
}
|
||||
|
||||
/// The bound ω² ≤ k λ_max(Σ b bᵀ) / (V m_min) on an element's highest frequency
|
||||
/// ([`modelcraft_fem::uniform`]), with the lumped masses of the undeformed element and
|
||||
/// ([`fieldcraft_fem::uniform`]), with the lumped masses of the undeformed element and
|
||||
/// `gradient_norm` = λ_max(Σ b bᵀ).
|
||||
pub fn frequency_bound<const N: usize>(k: &UniformStrain<N>, gradient_norm: f64, modulus: f64, mass: &[f64; N]) -> f64 {
|
||||
let lightest = mass.iter().copied().fold(f64::INFINITY, f64::min);
|
||||
@@ -353,7 +353,7 @@ pub fn hex_scales(
|
||||
mass: &[f64; 8],
|
||||
mat: &Material,
|
||||
density: f64,
|
||||
hourglass: modelcraft_model::Hourglass,
|
||||
hourglass: fieldcraft_model::Hourglass,
|
||||
) -> HexScales {
|
||||
let norm = k.gradient_norm();
|
||||
// h: the edge of the cube with the element's volume and gradient norm.
|
||||
@@ -439,7 +439,7 @@ fn run<T: Send>(
|
||||
) -> Result<()> {
|
||||
let mut all = pieces::<T>(states, count, &index)?;
|
||||
for round in all.chunks_mut(ROUND) {
|
||||
modelcraft_parallel::for_each_mut(round, |_, piece| {
|
||||
fieldcraft_parallel::for_each_mut(round, |_, piece| {
|
||||
let Piece { items, base, states, out } = piece;
|
||||
*out = items
|
||||
.clone()
|
||||
@@ -476,7 +476,7 @@ fn hexahedron(sys: &System, h: &crate::system::Hex, m: &Motion, es: &mut Element
|
||||
if es.failed.is_some() {
|
||||
return Ok(Outcome::Skipped);
|
||||
}
|
||||
let hourglass = modelcraft_model::Hourglass { kind: sys.hourglass, coefficient: sys.hourglass_coefficient };
|
||||
let hourglass = fieldcraft_model::Hourglass { kind: sys.hourglass, coefficient: sys.hourglass_coefficient };
|
||||
let e = Nodal::gather(sys, &h.nodes, m);
|
||||
let x_now = e.positions(0.0);
|
||||
let (mid, now) = if sys.nonlinear {
|
||||
@@ -607,7 +607,7 @@ pub fn internal_forces(sys: &System, m: &Motion, state: &mut State, ws: &mut Wor
|
||||
// Pass 1: each tetrahedron's increment, and each node's mean volumetric increment.
|
||||
let increments = {
|
||||
let st: &State = state;
|
||||
modelcraft_parallel::map_grained(tets.len(), BLOCK, |k| match tets.get(k) {
|
||||
fieldcraft_parallel::map_grained(tets.len(), BLOCK, |k| match tets.get(k) {
|
||||
Some(t) => tetrahedron_increment(sys, t, m, st),
|
||||
None => Ok(None),
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
//! The run starts from u = 0 and the initial velocities with the half increment
|
||||
//! v^{½} = v^0 + ½Δt^{½} a^0.
|
||||
|
||||
use modelcraft_model::{ExplicitDynamic, Model, OutputVariable, Procedure, Step};
|
||||
use fieldcraft_model::{ExplicitDynamic, Model, OutputVariable, Procedure, Step};
|
||||
|
||||
use crate::elements::{Motion, State, Workspace, internal_forces};
|
||||
use crate::result::{ControllingElement, Energies, ErodedElement, ExplicitResult, Frame, History};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! ModelCraft's explicit dynamics solver: transient response of a model's explicit dynamic step
|
||||
//! FieldCraft's explicit dynamics solver: transient response of a model's explicit dynamic step
|
||||
//! by central-difference time integration.
|
||||
//!
|
||||
//! The method is the explicit central-difference scheme of T. Belytschko, W. K. Liu, B. Moran,
|
||||
@@ -24,7 +24,7 @@
|
||||
//! docs/solvers/literature.md, Parts F and J). Without it the gradients stay those of the
|
||||
//! undeformed mesh (small displacements).
|
||||
//!
|
||||
//! Elements are updated on threads ([`modelcraft_parallel`]), each owning its state, and their
|
||||
//! Elements are updated on threads ([`fieldcraft_parallel`]), each owning its state, and their
|
||||
//! nodal forces are added in label order as a sequential loop adds them, with no parallel
|
||||
//! reductions, so the same model gives the same numbers bit for bit on any number of threads;
|
||||
//! sizes, increments and output are capped by [`Limits`].
|
||||
@@ -46,14 +46,14 @@ pub use result::{ControllingElement, Energies, ErodedElement, ExplicitResult, Fr
|
||||
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Model(#[from] modelcraft_model::Error),
|
||||
Model(#[from] fieldcraft_model::Error),
|
||||
#[error("invalid input: {0}")]
|
||||
Invalid(String),
|
||||
#[error("not supported yet: {0}")]
|
||||
NotSupported(String),
|
||||
/// An element kernel failed (inverted geometry, …).
|
||||
#[error("element {label}: {source}")]
|
||||
Element { label: u32, source: modelcraft_fem::Error },
|
||||
Element { label: u32, source: fieldcraft_fem::Error },
|
||||
#[error("limit reached: {0}")]
|
||||
Limit(String),
|
||||
/// The solution stopped being finite.
|
||||
@@ -68,7 +68,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl Error {
|
||||
/// A kernel error, naming the element.
|
||||
pub fn element(label: u32, source: modelcraft_fem::Error) -> Error {
|
||||
pub fn element(label: u32, source: fieldcraft_fem::Error) -> Error {
|
||||
Error::Element { label, source }
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ pub struct Limits {
|
||||
|
||||
impl Limits {
|
||||
pub const MAX_DOFS: usize = 6_000_000;
|
||||
pub const MAX_INCREMENTS: u64 = modelcraft_model::steps::MAX_INCREMENTS;
|
||||
pub const MAX_INCREMENTS: u64 = fieldcraft_model::steps::MAX_INCREMENTS;
|
||||
pub const MAX_OUTPUT_VALUES: usize = 50_000_000;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
//! ([`crate::discrete`]), prescribed velocities, rigid walls, initial velocities and the load
|
||||
//! vector of an explicit dynamic step.
|
||||
|
||||
use modelcraft_fem::material::Elasticity;
|
||||
use modelcraft_fem::plasticity::J2;
|
||||
use modelcraft_fem::uniform::{Hex8, Tet4, modulus_bound};
|
||||
use modelcraft_fem::{DofKind, DofLayout, DofSet, face};
|
||||
use modelcraft_model::{
|
||||
use fieldcraft_fem::material::Elasticity;
|
||||
use fieldcraft_fem::plasticity::J2;
|
||||
use fieldcraft_fem::uniform::{Hex8, Tet4, modulus_bound};
|
||||
use fieldcraft_fem::{DofKind, DofLayout, DofSet, face};
|
||||
use fieldcraft_model::{
|
||||
Elastic, ElementKind, ExplicitDynamic, HourglassKind, Integration, Load, Model, Resolved, SectionKind, Step, StressRate, Target,
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ pub struct Material {
|
||||
/// Dilatational wave speed √(M/ρ), with M the largest normal stiffness (λ + 2μ when
|
||||
/// isotropic).
|
||||
pub wave_speed: f64,
|
||||
/// The bound of [`modelcraft_fem::uniform::modulus_bound`].
|
||||
/// The bound of [`fieldcraft_fem::uniform::modulus_bound`].
|
||||
pub modulus: f64,
|
||||
/// The largest normal stiffness M (λ + 2μ when isotropic): c = √(M/ρ).
|
||||
pub stiffest: f64,
|
||||
@@ -143,7 +143,7 @@ fn material(model: &Model, name: &str) -> Result<Material> {
|
||||
.density
|
||||
.filter(|d| *d > 0.0 && d.is_finite())
|
||||
.ok_or_else(|| invalid(format!("material `{name}` has no density, and explicit dynamics needs one")))?;
|
||||
let d = modelcraft_fem::elasticity(elastic).map_err(|e| invalid(format!("material `{name}`: {e}")))?;
|
||||
let d = fieldcraft_fem::elasticity(elastic).map_err(|e| invalid(format!("material `{name}`: {e}")))?;
|
||||
let stiffest = d[0][0].max(d[1][1]).max(d[2][2]);
|
||||
// The model keeps plasticity, damage and erosion to isotropic materials.
|
||||
let j2 = match *elastic {
|
||||
@@ -171,7 +171,7 @@ fn material(model: &Model, name: &str) -> Result<Material> {
|
||||
}
|
||||
|
||||
/// The section's elements of a kind the explicit solver has, or why not.
|
||||
fn check_formulation(kind: ElementKind, section_name: &str, f: &modelcraft_model::Formulation) -> Result<()> {
|
||||
fn check_formulation(kind: ElementKind, section_name: &str, f: &fieldcraft_model::Formulation) -> Result<()> {
|
||||
let refuse = |why: &str| Err(Error::NotSupported(format!("section `{section_name}`: {why}")));
|
||||
if f.hybrid {
|
||||
return refuse("hybrid elements in explicit dynamics");
|
||||
@@ -327,7 +327,7 @@ impl System {
|
||||
)));
|
||||
}
|
||||
let young =
|
||||
modelcraft_fem::truss::young_modulus(source.elastic.as_ref().ok_or_else(|| Error::Internal("truss material".into()))?)
|
||||
fieldcraft_fem::truss::young_modulus(source.elastic.as_ref().ok_or_else(|| Error::Internal("truss material".into()))?)
|
||||
.map_err(|e| invalid(format!("material `{}` of element {label}: {e}", mat.name)))?;
|
||||
let x: Vec<[f64; 3]> = idx.iter().map(|i| sys.xyz.get(*i).copied().unwrap_or_default()).collect();
|
||||
let rod = Rod::new(label, index, kind, idx.clone(), &x, mi, crate::discrete::RodSection { young, density: rho, area })?;
|
||||
@@ -518,7 +518,7 @@ impl System {
|
||||
self.each_element(chosen, |nodes, weights, rho| {
|
||||
for (node, w) in nodes.iter().zip(weights) {
|
||||
let x = xyz.get(*node).copied().unwrap_or_default();
|
||||
let a = modelcraft_model::centrifugal_acceleration(*angular_velocity, *point, *axis, x);
|
||||
let a = fieldcraft_model::centrifugal_acceleration(*angular_velocity, *point, *axis, x);
|
||||
add_at(&mut f, *node, &a.map(|c| rho * w * c));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
//!
|
||||
//! Run with `--nocapture` to see the errors.
|
||||
|
||||
use modelcraft_explicit::elements::{Motion, State, Workspace, internal_forces};
|
||||
use modelcraft_explicit::system::System;
|
||||
use modelcraft_explicit::{ExplicitResult, Failure, Limits, solve_explicit};
|
||||
use modelcraft_model::*;
|
||||
use fieldcraft_explicit::elements::{Motion, State, Workspace, internal_forces};
|
||||
use fieldcraft_explicit::system::System;
|
||||
use fieldcraft_explicit::{ExplicitResult, Failure, Limits, solve_explicit};
|
||||
use fieldcraft_model::*;
|
||||
|
||||
/// A block [0, size] of `n` cells: one hex8 per cell, or six tet4 (Kuhn's split).
|
||||
fn block(n: [usize; 3], size: [f64; 3], kind: ElementKind) -> Model {
|
||||
@@ -179,7 +179,7 @@ impl Driver {
|
||||
self.try_go(to, dt).unwrap()
|
||||
}
|
||||
|
||||
fn try_go(&mut self, to: impl Fn([f64; 3]) -> [f64; 3], dt: f64) -> modelcraft_explicit::Result<[f64; 6]> {
|
||||
fn try_go(&mut self, to: impl Fn([f64; 3]) -> [f64; 3], dt: f64) -> fieldcraft_explicit::Result<[f64; 6]> {
|
||||
let mut du = vec![0.0; self.u.len()];
|
||||
for (i, x0) in self.sys.xyz.iter().enumerate() {
|
||||
let x = to(*x0);
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use modelcraft_explicit::{ExplicitResult, Limits, solve_explicit};
|
||||
use modelcraft_fem::truss::axial;
|
||||
use modelcraft_model::*;
|
||||
use fieldcraft_explicit::{ExplicitResult, Limits, solve_explicit};
|
||||
use fieldcraft_fem::truss::axial;
|
||||
use fieldcraft_model::*;
|
||||
|
||||
fn model(step: ExplicitDynamic) -> Model {
|
||||
let mut m = Model::default();
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//! a free bar by α ΔT L without stress; a bar held at both ends carries σ = −E α ΔT, and with
|
||||
//! perfectly plastic J2 the stress stops at the yield stress.
|
||||
|
||||
use modelcraft_explicit::{Limits, solve_explicit};
|
||||
use modelcraft_model::*;
|
||||
use fieldcraft_explicit::{Limits, solve_explicit};
|
||||
use fieldcraft_model::*;
|
||||
|
||||
const YOUNG: f64 = 1.0e4;
|
||||
const ALPHA: f64 = 1.0e-4;
|
||||
@@ -115,7 +115,7 @@ fn a_clamped_bar_carries_minus_e_alpha_dt_up_to_yield() {
|
||||
let r = solve_explicit(&bar(kind, true, Some(plastic)), None, &Limits::default()).unwrap();
|
||||
let last = r.frames.last().unwrap();
|
||||
for (s, ep) in last.stress.iter().zip(&last.plastic_strain) {
|
||||
let mises = modelcraft_fem::von_mises(s);
|
||||
let mises = fieldcraft_fem::von_mises(s);
|
||||
assert!((mises - 0.5 * sigma.abs()).abs() < 0.02 * sigma.abs(), "{kind:?}: {s:?}");
|
||||
assert!(*ep > 0.0, "{kind:?}: plastic strain {ep}");
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
//! The bar has Poisson's ratio 0, so a uniaxial stress wave travels at the rod speed c = √(E/ρ)
|
||||
//! without lateral inertia effects (d'Alembert's solution of the one-dimensional wave equation).
|
||||
|
||||
use modelcraft_explicit::{ExplicitResult, Limits, solve_explicit};
|
||||
use modelcraft_model::*;
|
||||
use fieldcraft_explicit::{ExplicitResult, Limits, solve_explicit};
|
||||
use fieldcraft_model::*;
|
||||
|
||||
const YOUNG: f64 = 1.0e4;
|
||||
const DENSITY: f64 = 1.0;
|
||||
|
||||
Reference in new issue
Block a user