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 <[email protected]> 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-solver"
|
||||
description = "ModelCraft's implicit solver: assembly, boundary conditions, sparse Cholesky, linear static and frequency analysis"
|
||||
name = "fieldcraft-solver"
|
||||
description = "FieldCraft's implicit solver: assembly, boundary conditions, sparse Cholesky, linear static and frequency analysis"
|
||||
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 }
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(§ion.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 §ion.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);
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
//! Both are modelled on one symmetric part (a quarter of the roof, an eighth of the cylinder)
|
||||
//! with quad4 (MITC4) shells on a structured mesh whose quadrilaterals are exactly flat.
|
||||
|
||||
use modelcraft_model::{
|
||||
use fieldcraft_model::{
|
||||
BoundaryCondition, Distribution, Elastic, ElementKind, Formulation, Load, Material, Model, Procedure, Section, SectionKind, Set, Step, Symmetry,
|
||||
Target,
|
||||
};
|
||||
use modelcraft_solver::{Limits, solve_static};
|
||||
use fieldcraft_solver::{Limits, solve_static};
|
||||
|
||||
/// A cylindrical shell patch: x from 0 to `length`, angle φ from 0 (top, z = R) to `arc`,
|
||||
/// y = R sin φ, z = R cos φ; n × n quad4 cells. Returns the model and the node at (x, φ).
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use modelcraft_model::{
|
||||
use fieldcraft_model::{
|
||||
BoundaryCondition, Coupling, CouplingKind, Dofs, Elastic, ElementFace, ElementKind, Formulation, Integration, Load, Material, Model, Procedure,
|
||||
ReferencePoint, Section, SectionKind, Set, Step, Symmetry, Target,
|
||||
};
|
||||
|
||||
@@ -6,8 +6,8 @@ mod common;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use common::*;
|
||||
use modelcraft_model::{BoundaryCondition, CouplingKind, Distribution, ElementKind, Load, Procedure, Symmetry, Target};
|
||||
use modelcraft_solver::{FrequencyOptions, Limits, StaticResult, solve_frequency, solve_static};
|
||||
use fieldcraft_model::{BoundaryCondition, CouplingKind, Distribution, ElementKind, Load, Procedure, Symmetry, Target};
|
||||
use fieldcraft_solver::{FrequencyOptions, Limits, StaticResult, solve_frequency, solve_static};
|
||||
|
||||
const ALL: [u8; 6] = [1, 2, 3, 4, 5, 6];
|
||||
|
||||
@@ -221,7 +221,7 @@ fn tube(nr: usize, nt: usize, nz: usize, ri: f64, ro: f64, length: f64) -> Grid
|
||||
.filter(|(_, p)| near(p[1], 1.0))
|
||||
.map(|(l, p)| (*l, *g.param.iter().find(|(_, o)| near(o[1], 0.0) && near(o[0], p[0]) && near(o[2], p[2])).unwrap().0))
|
||||
.collect();
|
||||
let elements: Vec<(u32, modelcraft_model::Element)> = g.model.elements().iter().map(|(l, e)| (*l, e.clone())).collect();
|
||||
let elements: Vec<(u32, fieldcraft_model::Element)> = g.model.elements().iter().map(|(l, e)| (*l, e.clone())).collect();
|
||||
for (label, e) in elements {
|
||||
let nodes: Vec<u32> = e.nodes.iter().map(|n| seam.iter().find(|(a, _)| a == n).map_or(*n, |(_, b)| *b)).collect();
|
||||
g.model.set_element(label, e.kind, nodes).unwrap();
|
||||
@@ -308,7 +308,7 @@ fn coupling_errors_name_the_problem() {
|
||||
g.load("m", moment(rp("Tip"), [1.0, 0.0, 0.0]));
|
||||
assert!(err(&g).contains("rotation about X is not coupled"), "{}", err(&g));
|
||||
|
||||
let loose = |g: &mut Grid| g.model.add_reference_point("Loose", modelcraft_model::ReferencePoint { xyz: [3.0, 0.0, 0.0], label: 0 }).unwrap();
|
||||
let loose = |g: &mut Grid| g.model.add_reference_point("Loose", fieldcraft_model::ReferencePoint { xyz: [3.0, 0.0, 0.0], label: 0 }).unwrap();
|
||||
|
||||
// A force on a reference point nothing couples.
|
||||
let mut g = base();
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
mod common;
|
||||
|
||||
use common::*;
|
||||
use modelcraft_model::*;
|
||||
use modelcraft_solver::{Limits, solve_static};
|
||||
use fieldcraft_model::*;
|
||||
use fieldcraft_solver::{Limits, solve_static};
|
||||
|
||||
const NU: f64 = 0.4999;
|
||||
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
//! bars, P / (1 + 2 cos³θ) in the vertical one.
|
||||
//! - rod3: constant-stress patch with off-centre middle nodes; a bar hanging under its own
|
||||
//! weight (quadratic displacement, exact at the nodes); curved members are checked in
|
||||
//! `modelcraft_fem::truss` (a ring's radial expansion gives the hoop strain δ/R exactly).
|
||||
//! `fieldcraft_fem::truss` (a ring's radial expansion gives the hoop strain δ/R exactly).
|
||||
//! - Spring–mass ω = √(k/m), springs along three skew directions, the two-mass chain, a bar with
|
||||
//! a tip mass ω = √(EA/(LM)), the torsional pendulum ω = √(k_t/J).
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use modelcraft_fem::truss::axial;
|
||||
use modelcraft_model::*;
|
||||
use modelcraft_solver::{FrequencyOptions, FrequencyResult, Limits, MassKind, StaticResult, solve_frequency, solve_static};
|
||||
use fieldcraft_fem::truss::axial;
|
||||
use fieldcraft_model::*;
|
||||
use fieldcraft_solver::{FrequencyOptions, FrequencyResult, Limits, MassKind, StaticResult, solve_frequency, solve_static};
|
||||
|
||||
const ZERO_Z: [Option<f64>; 6] = [None, None, Some(0.0), None, None, None];
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use modelcraft_model::{
|
||||
use fieldcraft_model::{
|
||||
BoundaryCondition, Elastic, ElementEdge, ElementKind, Formulation, Integration, Load, Material, Model, Procedure, Section, SectionKind, Set,
|
||||
Step, Symmetry, Target,
|
||||
};
|
||||
use modelcraft_solver::{FrequencyOptions, Limits, StaticResult, solve_frequency, solve_static};
|
||||
use fieldcraft_solver::{FrequencyOptions, Limits, StaticResult, solve_frequency, solve_static};
|
||||
|
||||
/// A structured 2D mesh of `n` cells on [0, 1]², mapped by `map`; nodes on the doubled lattice so
|
||||
/// quadratic elements take their mid-side nodes from the map.
|
||||
@@ -540,15 +540,15 @@ fn two_dimensional_results_are_deterministic_and_errors_name_the_problem() {
|
||||
assert_eq!(g.solve(), g.solve());
|
||||
// A coupling is not supported in 2D yet.
|
||||
let mut g = make();
|
||||
g.model.add_reference_point("RP", modelcraft_model::ReferencePoint { xyz: [0.0, 2.0, 0.0], label: 0 }).unwrap();
|
||||
g.model.add_reference_point("RP", fieldcraft_model::ReferencePoint { xyz: [0.0, 2.0, 0.0], label: 0 }).unwrap();
|
||||
g.model
|
||||
.add_coupling(
|
||||
"c",
|
||||
modelcraft_model::Coupling {
|
||||
fieldcraft_model::Coupling {
|
||||
reference_point: "RP".into(),
|
||||
set: "bottom".into(),
|
||||
kind: modelcraft_model::CouplingKind::Kinematic,
|
||||
dofs: modelcraft_model::Dofs::all(),
|
||||
kind: fieldcraft_model::CouplingKind::Kinematic,
|
||||
dofs: fieldcraft_model::Dofs::all(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -571,8 +571,8 @@ fn two_dimensional_results_are_deterministic_and_errors_name_the_problem() {
|
||||
/// Give the material an expansion coefficient and heat every node by `dt` above the reference.
|
||||
fn heat(g: &mut Grid, alpha: f64, dt: f64) {
|
||||
let mut m = g.model.material("Mat").unwrap().clone();
|
||||
m.thermal = Some(modelcraft_model::Thermal {
|
||||
expansion: Some(modelcraft_model::Directional::Isotropic(alpha)),
|
||||
m.thermal = Some(fieldcraft_model::Thermal {
|
||||
expansion: Some(fieldcraft_model::Directional::Isotropic(alpha)),
|
||||
reference_temperature: 20.0,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use modelcraft_model::{
|
||||
use fieldcraft_model::{
|
||||
BeamProfile, BoundaryCondition, Distribution, Elastic, ElementFace, ElementKind, Formulation, Load, Material, Model, Procedure, Section,
|
||||
SectionKind, Set, Step, Target,
|
||||
};
|
||||
use modelcraft_solver::{FrequencyOptions, Limits, MassKind, StaticResult, solve_frequency, solve_static};
|
||||
use fieldcraft_solver::{FrequencyOptions, Limits, MassKind, StaticResult, solve_frequency, solve_static};
|
||||
|
||||
/// A model under construction: nodes and elements numbered from 1, one material, one step.
|
||||
struct Build {
|
||||
@@ -343,7 +343,7 @@ fn cantilever_beam_deflection_is_exact() {
|
||||
// A stocky I beam, so shear deformation matters: δ = P L³/(3 E I) + P L/(G A_s) in each plane.
|
||||
let profile = BeamProfile::I { height: 200.0, width: 100.0, web: 6.0, flange: 10.0 };
|
||||
let length: f64 = 1000.0;
|
||||
let p = modelcraft_fem::beam::properties(&profile, 0.3);
|
||||
let p = fieldcraft_fem::beam::properties(&profile, 0.3);
|
||||
let (e, g) = (210e3, 210e3 / 2.6);
|
||||
for n in [1, 4] {
|
||||
for (along_y, name) in [(true, "weak"), (false, "strong")] {
|
||||
@@ -377,7 +377,7 @@ fn cantilever_beam_frequencies() {
|
||||
// Euler–Bernoulli ω_k = (β_k L)² √(E I / (ρ A L⁴)).
|
||||
let profile = BeamProfile::Pipe { radius: 10.0, thickness: 1.0 };
|
||||
let length: f64 = 10000.0;
|
||||
let p = modelcraft_fem::beam::properties(&profile, 0.3);
|
||||
let p = fieldcraft_fem::beam::properties(&profile, 0.3);
|
||||
let base = (210e3 * p.iy / (7.85e-9 * p.area * length.powi(4))).sqrt() / (2.0 * std::f64::consts::PI);
|
||||
let beta = [1.875_104_068_711_961, 4.694_091_132_974_175, 7.854_757_438_237_613];
|
||||
for mass in [MassKind::Consistent, MassKind::Lumped] {
|
||||
@@ -443,7 +443,7 @@ fn portal_frame_matches_the_slope_deflection_solution() {
|
||||
b.force("push", vec![n as u32 + 1], [load, 0.0, 0.0]);
|
||||
let r = b.solve();
|
||||
let g = e / 2.6;
|
||||
let (pc, pb) = (modelcraft_fem::beam::properties(&column, 0.3), modelcraft_fem::beam::properties(&girder, 0.3));
|
||||
let (pc, pb) = (fieldcraft_fem::beam::properties(&column, 0.3), fieldcraft_fem::beam::properties(&girder, 0.3));
|
||||
let (phi_c, phi_b) = (12.0 * e * pc.iy / (g * pc.shear_z * h * h), 12.0 * e * pb.iy / (g * pb.shear_z * l * l));
|
||||
let (a, bb) = (e * pc.iy / (h * (1.0 + phi_c)), e * pb.iy / (l * (1.0 + phi_b)));
|
||||
let c = 6.0 * a / (h * (a * (4.0 + phi_c) + 6.0 * bb));
|
||||
@@ -499,7 +499,7 @@ fn rods_continue_a_solid_column() {
|
||||
}
|
||||
let rods: Vec<_> = r.elements.iter().filter(|e| e.kind == ElementKind::Rod2).collect();
|
||||
assert_eq!(rods.len(), 4);
|
||||
assert!(rods.iter().flat_map(|e| &e.nodal).all(|s| rel(modelcraft_fem::von_mises(s), 4.0) < 1e-12), "{rods:?}");
|
||||
assert!(rods.iter().flat_map(|e| &e.nodal).all(|s| rel(fieldcraft_fem::von_mises(s), 4.0) < 1e-12), "{rods:?}");
|
||||
// Solid nodes have no rotations.
|
||||
assert!(r.rotation.iter().all(|x| *x == [0.0; 3]));
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
mod common;
|
||||
|
||||
use common::*;
|
||||
use modelcraft_model::*;
|
||||
use modelcraft_solver::{Limits, solve_heat, solve_static};
|
||||
use fieldcraft_model::*;
|
||||
use fieldcraft_solver::{Limits, solve_heat, solve_static};
|
||||
|
||||
const K: f64 = 45.0;
|
||||
|
||||
@@ -26,7 +26,7 @@ fn hold(g: &mut Grid, name: &str, value: f64, f: impl Fn([f64; 3]) -> bool) {
|
||||
g.bc(name, BoundaryCondition::Temperature { target: Target::Set(name.into()), value });
|
||||
}
|
||||
|
||||
fn temperature_at(r: &modelcraft_solver::HeatResult, label: u32) -> f64 {
|
||||
fn temperature_at(r: &fieldcraft_solver::HeatResult, label: u32) -> f64 {
|
||||
r.temperature()[r.nodes.binary_search(&label).unwrap()]
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ fn heat_steps_refuse_what_they_cannot_solve() {
|
||||
|
||||
/// One rod3 or two rod2 along X over [0, `len`], heated by `dt`, fixed at x = 0, the other
|
||||
/// nodes held in Y and Z, and the end held along X too when `held`.
|
||||
fn heated_rod(kind: ElementKind, len: f64, alpha: [f64; 3], dt: f64, held: bool) -> modelcraft_solver::StaticResult {
|
||||
fn heated_rod(kind: ElementKind, len: f64, alpha: [f64; 3], dt: f64, held: bool) -> fieldcraft_solver::StaticResult {
|
||||
let mut m = Model::default();
|
||||
for (label, x) in [(1, 0.0), (2, len), (3, 0.5 * len)] {
|
||||
m.set_node(label, [x, 0.0, 0.0]).unwrap();
|
||||
|
||||
@@ -6,8 +6,8 @@ mod common;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use common::*;
|
||||
use modelcraft_model::{BoundaryCondition, ElementKind, Load, Procedure, Symmetry, Target};
|
||||
use modelcraft_solver::{FrequencyOptions, Limits, MassKind, StaticResult, solve_frequency, solve_static};
|
||||
use fieldcraft_model::{BoundaryCondition, ElementKind, Load, Procedure, Symmetry, Target};
|
||||
use fieldcraft_solver::{FrequencyOptions, Limits, MassKind, StaticResult, solve_frequency, solve_static};
|
||||
|
||||
fn static_solve(g: &Grid) -> StaticResult {
|
||||
solve_static(&g.model, Some("Step"), &Limits::default()).unwrap()
|
||||
@@ -50,7 +50,7 @@ fn patch_test_every_element() {
|
||||
let x = g.model.node(label).unwrap().xyz;
|
||||
let u = field(x);
|
||||
let set = format!("b{i}");
|
||||
g.model.add_set(&set, modelcraft_model::Set { nodes: vec![label], ..Default::default() }).unwrap();
|
||||
g.model.add_set(&set, fieldcraft_model::Set { nodes: vec![label], ..Default::default() }).unwrap();
|
||||
g.bc(
|
||||
&set,
|
||||
BoundaryCondition::Displacement { target: Target::Set(set.clone()), values: [Some(u[0]), Some(u[1]), Some(u[2]), None, None, None] },
|
||||
@@ -132,7 +132,7 @@ fn gravity_and_body_force_totals() {
|
||||
/// Cantilever L × b × h, clamped at x = 0, a shear traction on the free end. Timoshenko
|
||||
/// deflection δ = P L³ / (3 E I) + P L / (κ G A), κ = 5/6, with ν = 0 so the clamp adds no
|
||||
/// lateral restraint.
|
||||
fn cantilever_tip(kind: ElementKind, formulation: modelcraft_model::Formulation, n: [usize; 3]) -> f64 {
|
||||
fn cantilever_tip(kind: ElementKind, formulation: fieldcraft_model::Formulation, n: [usize; 3]) -> f64 {
|
||||
let (l, b, h, young, p) = (10.0, 1.0, 1.0, 1000.0, 1.0);
|
||||
let mut g = grid(n, kind, |q| [l * q[0], b * q[1], h * q[2]]);
|
||||
g.define(iso(young, 0.0), None, formulation, Procedure::Static {});
|
||||
@@ -377,7 +377,7 @@ fn plastic_materials_solve_elastically_with_a_warning() {
|
||||
g.define(iso(1000.0, 0.3), None, full(), Procedure::Static {});
|
||||
if plastic {
|
||||
let mut m = g.model.material("Mat").unwrap().clone();
|
||||
m.plastic = Some(modelcraft_model::Plastic::Bilinear { yield_stress: 1.0, hardening_modulus: 0.0, hardening: Default::default() });
|
||||
m.plastic = Some(fieldcraft_model::Plastic::Bilinear { yield_stress: 1.0, hardening_modulus: 0.0, hardening: Default::default() });
|
||||
g.model.replace_material("Mat", m).unwrap();
|
||||
}
|
||||
g.fixed("root", |q| near(q[0], 0.0));
|
||||
|
||||
Reference in new issue
Block a user