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:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-13 23:51:40 -04:00
1 parent 9bc4e1b182
commit d5bd74570f
268 files changed
+1666 -1666

No files matched your search

+8 -8
View File
@@ -1,15 +1,15 @@
//! Linear static and frequency analysis of a model's step, and their results.
use modelcraft_fem::ElementType;
use modelcraft_fem::beam::BeamForces;
use modelcraft_fem::element::{Extrapolation, extrapolation};
use modelcraft_fem::quadrature::Rule;
use modelcraft_fem::shell::ShellStress;
use modelcraft_fem::{DofKind, DofLayout, PointStress, von_mises};
use modelcraft_model::{ElementKind, Model, Procedure, Resolved, Step};
use fieldcraft_fem::ElementType;
use fieldcraft_fem::beam::BeamForces;
use fieldcraft_fem::element::{Extrapolation, extrapolation};
use fieldcraft_fem::quadrature::Rule;
use fieldcraft_fem::shell::ShellStress;
use fieldcraft_fem::{DofKind, DofLayout, PointStress, von_mises};
use fieldcraft_model::{ElementKind, Model, Procedure, Resolved, Step};
use serde::Serialize;
use modelcraft_parallel as parallel;
use fieldcraft_parallel as parallel;
use crate::cholesky::{Factor, FactorError, UpperColumns};
use crate::constraints::{Budget, Reduction, coupling_rows};
+1 -1
View File
@@ -34,7 +34,7 @@
//! zero-stiffness direction); the factorization stops and names the column: the first such column
//! in the postorder, whichever thread meets it.
use modelcraft_parallel as parallel;
use fieldcraft_parallel as parallel;
use crate::{Error, Result};
+3 -3
View File
@@ -51,9 +51,9 @@
use std::collections::BTreeMap;
use modelcraft_fem::dense::inverse3;
use modelcraft_fem::{DofKind, DofSet};
use modelcraft_model::{CouplingKind, Model, Resolved};
use fieldcraft_fem::dense::inverse3;
use fieldcraft_fem::{DofKind, DofSet};
use fieldcraft_model::{CouplingKind, Model, Resolved};
use crate::sparse::Csr;
use crate::system::Mesh;
+5 -5
View File
@@ -1,6 +1,6 @@
//! Point masses, rotary inertia, springs and dashpots in the implicit system.
//!
//! The model resolves each definition to its nodes ([`modelcraft_model::Resolved::masses`] and
//! The model resolves each definition to its nodes ([`fieldcraft_model::Resolved::masses`] and
//! `springs`); here each copy is turned into global degrees of freedom and its matrix:
//!
//! - a point mass adds m_x, m_y, m_z on the node's translations and its inertia tensor J (3 × 3,
@@ -12,11 +12,11 @@
//! - a dashpot has the same form on velocities; static and frequency steps have no velocities, so
//! they ignore dashpots with a warning (damping enters dynamic steps).
//!
//! What each copy needs at its nodes ([`needs`]) goes into the mesh's [`modelcraft_fem::DofLayout`]
//! What each copy needs at its nodes ([`needs`]) goes into the mesh's [`fieldcraft_fem::DofLayout`]
//! like an element's declared kinds; [`dof_of`] then finds its unknowns there.
use modelcraft_fem::{DofKind, DofSet};
use modelcraft_model::{Resolved, ResolvedSpring};
use fieldcraft_fem::{DofKind, DofSet};
use fieldcraft_model::{Resolved, ResolvedSpring};
use crate::sparse::{Csr, add_entry};
use crate::system::Mesh;
@@ -103,7 +103,7 @@ impl Discrete {
.filter(|d| m.mass[*d] != 0.0 && DofKind::translation(*d).is_some_and(|k| planar.contains(k)))
.map(|d| dof_of(mesh, m.node, d).map(|dof| (dof, m.mass[d])).ok_or_else(|| missing("point mass", &m.name, m.node, d)))
.collect::<Result<_>>()?;
let inertia = modelcraft_model::discrete::inertia_matrix(m.inertia);
let inertia = fieldcraft_model::discrete::inertia_matrix(m.inertia);
let rotations = if m.inertia.iter().any(|c| *c != 0.0) {
(3..6).map(|d| dof_of(mesh, m.node, d).ok_or_else(|| missing("point mass", &m.name, m.node, d))).collect::<Result<_>>()?
} else {
+1 -1
View File
@@ -286,7 +286,7 @@ pub fn lanczos(op: &impl Operator, shift: f64, wanted: Wanted, block: usize, max
// W = Op Q_j.
let mut w: Vec<Vec<f64>> = Vec::with_capacity(b);
// The block's solves are independent: one task each.
for y in modelcraft_parallel::map(mcur.len(), |i| {
for y in fieldcraft_parallel::map(mcur.len(), |i| {
let mut y = mcur.get(i).cloned().unwrap_or_default();
op.solve(&mut y).map(|()| y)
}) {
+6 -6
View File
@@ -1,9 +1,9 @@
//! Heat transfer: steady and transient conduction with prescribed temperatures, surface fluxes,
//! convection and internal heat sources.
//!
//! Every node of the solid elements carries one temperature ([`modelcraft_fem::heat::HEAT_DOFS`]).
//! Every node of the solid elements carries one temperature ([`fieldcraft_fem::heat::HEAT_DOFS`]).
//! The conductivity K (with the convection films) and capacity C are assembled on the node
//! graph in element label order ([`modelcraft_fem::heat`]); prescribed temperatures are
//! graph in element label order ([`fieldcraft_fem::heat`]); prescribed temperatures are
//! eliminated, the free ones numbered by nested dissection and solved by sparse Cholesky.
//!
//! - **Steady**: K T = f.
@@ -16,10 +16,10 @@
//! The heat flux −k∇T is reported at the integration points and averaged at the nodes; the heat
//! flow at prescribed temperatures is the reaction K T − f (C Ṫ included in transient steps).
use modelcraft_fem::element::{Extrapolation, extrapolation};
use modelcraft_fem::heat::{Conduction, PointFlux, face_film, face_weights};
use modelcraft_fem::{DofLayout, DofSet};
use modelcraft_model::{ElementKind, Load, Model, Procedure, Resolved, SectionKind, Step, TEMPERATURE_DOF};
use fieldcraft_fem::element::{Extrapolation, extrapolation};
use fieldcraft_fem::heat::{Conduction, PointFlux, face_film, face_weights};
use fieldcraft_fem::{DofLayout, DofSet};
use fieldcraft_model::{ElementKind, Load, Model, Procedure, Resolved, SectionKind, Step, TEMPERATURE_DOF};
use crate::cholesky::{Factor, FactorError, UpperColumns};
use crate::ordering::nested_dissection;
+7 -7
View File
@@ -1,9 +1,9 @@
//! ModelCraft's implicit solver: linear static and frequency analysis of a model's steps.
//! FieldCraft's implicit solver: linear static and frequency analysis of a model's steps.
//!
//! The pipeline, for one step of a [`modelcraft_model::Model`]:
//! 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
//! ([`modelcraft_fem`] kernels); the step's boundary conditions give the prescribed degrees of
//! ([`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.
@@ -16,7 +16,7 @@
//! kind of factorization.
//!
//! Element matrices and stresses, the factorization's subtrees and dense tiles, the Lanczos
//! block solves and matrix products run on threads ([`modelcraft_parallel`]), but every number
//! 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`].
@@ -44,14 +44,14 @@ pub use heat::{HeatResult, solve_heat};
#[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, a formulation it lacks, …).
#[error("element {label}: {source}")]
Element { label: u32, source: modelcraft_fem::Error },
Element { label: u32, source: fieldcraft_fem::Error },
/// The stiffness (or shifted matrix) is singular.
#[error("{0}")]
Singular(String),
@@ -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 }
}
}
+3 -3
View File
@@ -7,7 +7,7 @@
//! label, then the element's own node order), so assembled values are the same bit for bit on
//! every run and platform.
use modelcraft_fem::{DofLayout, DofSet};
use fieldcraft_fem::{DofLayout, DofSet};
use crate::{Error, Result};
@@ -34,7 +34,7 @@ impl Csr {
/// Rows are computed in parallel blocks; each row's sum runs in column order.
pub fn mul(&self, x: &[f64], y: &mut [f64]) {
let n = self.n.min(y.len());
modelcraft_parallel::chunks_mut(y.get_mut(..n).unwrap_or_default(), ROWS, |b, block| {
fieldcraft_parallel::chunks_mut(y.get_mut(..n).unwrap_or_default(), ROWS, |b, block| {
for (i, yr) in block.iter_mut().enumerate() {
let (cols, vals) = self.row(b * ROWS + i);
*yr = cols.iter().zip(vals).map(|(c, v)| v * x.get(*c).copied().unwrap_or(0.0)).sum();
@@ -130,7 +130,7 @@ impl NodeGraph {
}
}
/// Most kinds a node carries ([`modelcraft_fem::DofKind::ALL`]).
/// Most kinds a node carries ([`fieldcraft_fem::DofKind::ALL`]).
const MAX_KINDS: usize = 8;
/// Where node b's block lies in node a's rows of a matrix built by [`NodeGraph::zero_matrix`],
+25 -25
View File
@@ -7,17 +7,17 @@
//! translations for solids, Ux and Uy for 2D elements), nodes by label, then six per coupled reference point (points by
//! name): translations along X, Y, Z and rotations about them.
use modelcraft_fem::beam::{Beam, BeamSection};
use modelcraft_fem::dense::Matrix;
use modelcraft_fem::element::{Formulation, Solid, ThermalStrain};
use modelcraft_fem::plane::{Plane, Theory};
use modelcraft_fem::shell::{Shell, ShellSection};
use modelcraft_fem::truss::{Truss, young_modulus};
use modelcraft_fem::{DofKind, DofLayout, DofSet, Elasticity, ElementDofs, PointStress, elasticity, face, plane};
use modelcraft_model::nodal::edge_pressure;
use fieldcraft_fem::beam::{Beam, BeamSection};
use fieldcraft_fem::dense::Matrix;
use fieldcraft_fem::element::{Formulation, Solid, ThermalStrain};
use fieldcraft_fem::plane::{Plane, Theory};
use fieldcraft_fem::shell::{Shell, ShellSection};
use fieldcraft_fem::truss::{Truss, young_modulus};
use fieldcraft_fem::{DofKind, DofLayout, DofSet, Elasticity, ElementDofs, PointStress, elasticity, face, plane};
use fieldcraft_model::nodal::edge_pressure;
use std::collections::BTreeSet;
use modelcraft_model::{Elastic, ElementKind, Integration, Load, Model, Resolved, SectionKind, Step, Target, centrifugal_acceleration};
use fieldcraft_model::{Elastic, ElementKind, Integration, Load, Model, Resolved, SectionKind, Step, Target, centrifugal_acceleration};
use crate::analysis::MassKind;
use crate::{Error, Result};
@@ -56,21 +56,21 @@ impl Kernel<'_> {
}
}
pub fn stiffness(&self, d: &Elasticity) -> modelcraft_fem::Result<modelcraft_fem::dense::Matrix> {
pub fn stiffness(&self, d: &Elasticity) -> fieldcraft_fem::Result<fieldcraft_fem::dense::Matrix> {
match self {
Kernel::Solid(s) => s.stiffness(d),
Kernel::Plane(p) => p.stiffness(d),
}
}
pub fn mass(&self, density: f64) -> modelcraft_fem::Result<modelcraft_fem::dense::Matrix> {
pub fn mass(&self, density: f64) -> fieldcraft_fem::Result<fieldcraft_fem::dense::Matrix> {
match self {
Kernel::Solid(s) => s.mass(density),
Kernel::Plane(p) => p.mass(density),
}
}
pub fn lumped_mass(&self, density: f64) -> modelcraft_fem::Result<Vec<f64>> {
pub fn lumped_mass(&self, density: f64) -> fieldcraft_fem::Result<Vec<f64>> {
match self {
Kernel::Solid(s) => s.lumped_mass(density),
Kernel::Plane(p) => p.lumped_mass(density),
@@ -78,7 +78,7 @@ impl Kernel<'_> {
}
/// Consistent nodal forces of a uniform force per volume, as 3-vectors (Z zero for 2D).
pub fn body_force(&self, b: [f64; 3]) -> modelcraft_fem::Result<Vec<[f64; 3]>> {
pub fn body_force(&self, b: [f64; 3]) -> fieldcraft_fem::Result<Vec<[f64; 3]>> {
match self {
Kernel::Solid(s) => s.body_force(b),
Kernel::Plane(p) => Ok(p.body_force_field(|_| b)?.into_iter().map(|v| [v[0], v[1], 0.0]).collect()),
@@ -86,7 +86,7 @@ impl Kernel<'_> {
}
/// Consistent nodal forces of a force per volume b(x), as 3-vectors (Z zero for 2D).
pub fn body_force_field(&self, b: impl Fn([f64; 3]) -> [f64; 3]) -> modelcraft_fem::Result<Vec<[f64; 3]>> {
pub fn body_force_field(&self, b: impl Fn([f64; 3]) -> [f64; 3]) -> fieldcraft_fem::Result<Vec<[f64; 3]>> {
match self {
Kernel::Solid(s) => s.body_force_field(b),
Kernel::Plane(p) => Ok(p.body_force_field(b)?.into_iter().map(|v| [v[0], v[1], 0.0]).collect()),
@@ -95,7 +95,7 @@ impl Kernel<'_> {
/// Integration-point stresses for nodal displacements given as 3-vectors, with a thermal
/// strain when given.
pub fn stresses_with(&self, d: &Elasticity, u: &[[f64; 3]], thermal: Option<&ThermalStrain>) -> modelcraft_fem::Result<Vec<PointStress>> {
pub fn stresses_with(&self, d: &Elasticity, u: &[[f64; 3]], thermal: Option<&ThermalStrain>) -> fieldcraft_fem::Result<Vec<PointStress>> {
match self {
Kernel::Solid(s) => s.stresses_with(d, u, thermal),
Kernel::Plane(p) => p.stresses_with(d, &u.iter().map(|v| [v[0], v[1]]).collect::<Vec<_>>(), thermal),
@@ -103,21 +103,21 @@ impl Kernel<'_> {
}
/// The equivalent nodal forces of a thermal strain, as 3-vectors (Z zero for 2D).
pub fn thermal_load(&self, d: &Elasticity, thermal: &ThermalStrain) -> modelcraft_fem::Result<Vec<[f64; 3]>> {
pub fn thermal_load(&self, d: &Elasticity, thermal: &ThermalStrain) -> fieldcraft_fem::Result<Vec<[f64; 3]>> {
match self {
Kernel::Solid(s) => s.thermal_load(d, thermal),
Kernel::Plane(p) => Ok(p.thermal_load(d, thermal)?.into_iter().map(|v| [v[0], v[1], 0.0]).collect()),
}
}
pub fn rule(&self) -> modelcraft_fem::quadrature::Rule {
pub fn rule(&self) -> fieldcraft_fem::quadrature::Rule {
match self {
Kernel::Solid(s) => s.rule(),
Kernel::Plane(p) => p.rule(),
}
}
pub fn ty(&self) -> &'static modelcraft_fem::ElementType {
pub fn ty(&self) -> &'static fieldcraft_fem::ElementType {
match self {
Kernel::Solid(s) => s.ty,
Kernel::Plane(p) => p.ty,
@@ -217,7 +217,7 @@ impl Structural {
}
impl StructuralKernel {
pub fn stiffness(&self) -> modelcraft_fem::Result<Matrix> {
pub fn stiffness(&self) -> fieldcraft_fem::Result<Matrix> {
match self {
StructuralKernel::Shell(s) => s.stiffness(),
StructuralKernel::Beam(b) => Ok(b.stiffness()),
@@ -225,7 +225,7 @@ impl StructuralKernel {
}
/// The consistent or lumped mass over every element degree of freedom.
pub fn mass(&self, density: f64, kind: MassKind) -> modelcraft_fem::Result<Matrix> {
pub fn mass(&self, density: f64, kind: MassKind) -> fieldcraft_fem::Result<Matrix> {
Ok(match (self, kind) {
(StructuralKernel::Shell(s), MassKind::Consistent) => s.mass(density)?,
(StructuralKernel::Shell(s), MassKind::Lumped) => {
@@ -242,7 +242,7 @@ impl StructuralKernel {
}
/// Consistent nodal forces and moments of a uniform force per volume, six per node.
pub fn body_force(&self, b: [f64; 3]) -> modelcraft_fem::Result<Vec<f64>> {
pub fn body_force(&self, b: [f64; 3]) -> fieldcraft_fem::Result<Vec<f64>> {
match self {
StructuralKernel::Shell(s) => s.body_force(b),
StructuralKernel::Beam(beam) => Ok(beam.body_force(b)),
@@ -405,8 +405,8 @@ impl Mesh {
)));
}
let set = resolved.sets.get(&section.set).ok_or_else(|| Error::Invalid(format!("set `{}` is not resolved", section.set)))?;
let bad = |e: modelcraft_fem::Error| match e {
modelcraft_fem::Error::NotSupported(m) => Error::NotSupported(format!("section `{name}`: {m}")),
let bad = |e: fieldcraft_fem::Error| match e {
fieldcraft_fem::Error::NotSupported(m) => Error::NotSupported(format!("section `{name}`: {m}")),
e => Error::Invalid(format!("section `{name}`, material `{}`: {e}", section.material)),
};
let shared = match &section.kind {
@@ -483,7 +483,7 @@ impl Mesh {
layout.element(&e.2, e.7);
}
for e in &trusses {
layout.element(&e.2, modelcraft_fem::ElementDofs::new(translations));
layout.element(&e.2, fieldcraft_fem::ElementDofs::new(translations));
}
for e in &structural {
layout.element(&e.2, ElementDofs::new(Structural::KINDS));
@@ -705,7 +705,7 @@ pub fn loads(model: &Model, resolved: &Resolved, mesh: &Mesh, step: &Step, activ
let xyz: Vec<[f64; 3]> = idx.iter().filter_map(|i| mesh.xyz.get(*i).copied()).collect();
let theory = e.theory.ok_or_else(|| Error::Internal("2D element without a theory".into()))?;
let forces = edge_pressure(&xyz, theory.extent(), *magnitude)
.map_err(|err| Error::element(g.element, modelcraft_fem::Error::Invalid(err.to_string())))?;
.map_err(|err| Error::element(g.element, fieldcraft_fem::Error::Invalid(err.to_string())))?;
for (i, v) in idx.iter().zip(forces) {
add(&mut f, &translations(mesh, *i)?, v);
}