Skip to content
jchatkinsonPublic

About

A thin, curated, WebAssembly-ready finite element analysis engine, written in Rust, covering the subset of structural element formulations, material models, and analysis types commonly used for buildings

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

92 Commits

Folders and files

Repository files navigation

Carapace

CI

A thin, curated, WebAssembly-native finite element analysis engine, written in Rust, covering the subset of structural element formulations, material models, and analysis types actually used in practice by pysees, Carapace's companion frontend. It's designed from day one to run efficiently inside a browser Web Worker as a .wasm module.

Carapace is heavily inspired by OpenSees / Xara but is not a port of either — it's a from-scratch reimplementation.

Features

Both a 2D (ux, uy, rz) and 3D (ux, uy, uz, rx, ry, rz) execution profile exist side by side as one generic implementation (const-generic over NDIM/NDOF), not a hand-duplicated second engine — see each element/section entry below for which profile(s) it covers. The structure and how to extend it are described in docs/architecture.md.

Elements

  • Truss (2D + 3D)
  • ZeroLength — per-DOF materials and Coulomb friction coupling (2D + 3D), with optional OpenSees-style -orient local axes (default: global axes)
  • ElasticBeamColumn — axial, bending, torsion, Linear/PDelta transforms (2D + 3D)
  • DispBeamColumn — fiber-discretized, displacement-based (2D uniaxial, 3D biaxial)
  • Tri3 (constant strain) and Quad4 (full 2x2 or enhanced incompatible-mode) plane-stress/plane-strain continuum elements (2D), with isotropic and orthotropic PlaneMaterials, body, edge-traction and edge-pressure loads, and Gauss-point recorders
  • Shell4 (MITC4 flat shell) and Shell3 (DKT/Allman flat triangle), ElasticMembranePlate section; 3D: nodal, pressure and self-weight loads, static and modal (lumped mass), resultant recorders; match OpenSees ShellMITC4 / ShellDKGT
  • Shell stiffness modifiers (ETABS-style f11 f22 f12 m11 m22 m12 v13 v23) on ElasticMembranePlate, scaling the individual terms of the section D. Breaks OpenSees compatibility except in the uniform case (see ../pysees/docs/shell-plan.md).
  • ZeroLengthSection — fiber section with optional independent springs (2D + 3D)
  • ForceBeamColumn — fiber-discretized, force-based/flexibility method (2D uniaxial, 3D biaxial)
  • 2D co-rotational transform (large-displacement geometry)
  • 3D co-rotational transform — scoped, deliberately shelved: unlike planar rotations (a single commuting scalar angle), 3D rotations don't commute, so an objective large-rotation formulation needs persistent per-element quaternion-tracked nodal state (updated at commit, not just a Corotational2d -style pure function of current position) plus a dense, easy-to-mis-sign analytic geometric-stiffness Hessian (Xara/OpenSees's CorotCrdTransf3d — its T matrix and five ksigma1–ksigma5 blocks). More complexity than currently justified; revisit only if a real model needs large 3D frame rotations.
  • Nonlinear torsion (3D frame torsion is a decoupled elastic G*J term today, not fiber-derived)

Materials

  • Elastic
  • ElasticPP (elastic-perfectly-plastic, with permanent set)
  • Gap (one-sided gap) / Ent (no-tension)
  • Steel01 (bilinear kinematic + isotropic hardening)
  • Steel02 (Menegotto-Pinto smooth transition curve)
  • Concrete01 (Kent-Scott-Park envelope, no tension)
  • Concrete02 (Concrete01 + linear tension softening)
  • Hysteretic (multi-linear pinching + damage backbone)
  • Pinching4 (four-point pinching + independent cyclic-damage rules)
  • Parallel / Series / MinMax (material combinators — composite stress/strain coupling and strain-bound failure wrapping over any of the above)

Analysis

  • DOF activation: a node DOF is an equation only if an element stiffens it, a constraint uses it as a master, it carries a mass, or it is fixed — truss-only nodes need no rotations fixed by hand, and a nodal load on a DOF nothing resists is reported as a model error instead of silently dropped
  • Static analysis: load control, displacement control; Linear and Newton (full/modified/initial-tangent, optional line search) algorithms
  • Arc-length continuation (Integrator::ArcLength): scaled spherical constraint, coupled displacement/load-factor Newton through load limits, softening and snap-back, adaptive radius with transactional cutbacks, stop criteria with exact landing, bifurcation diagnostics (design and verification in docs/arclength.md)
  • ConvergenceTest::Combined: per-equation force/moment equilibrium, with every Newton convergence check made at the accepted (post-update) state
  • Multi-phase/staged analysis with frozen (held-constant) load patterns
  • Modal analysis (Lanczos eigensolver)
  • Transient analysis (Newmark-β, Rayleigh damping, ground motion), including a Newton corrector (TransientAnalysis::with_algorithm) for nonlinear response driven dynamically — Algorithm::Linear (one effective-system solve per step, exact only for linear-material response) remains the default
  • TangentStrategy (current/reuse-at-step-start/initial-tangent), optional LineSearch (bisection/regula falsi), and KrylovNewton (stale-tangent + Krylov-subspace acceleration) — one shared iteration driver reused by both static and transient analysis (design in docs/algorithms.md §0–§6.1)
  • Sparse direct solver, with tangent-factorization caching (SparseSolver::factor/SparseFactorization) reused across iterations by TangentStrategy::ReuseAtStepStart/Initial
  • Event-to-event stepping (design in the archived algorithm design §9–§17, not yet implemented)

Constraints

  • equal_dof (2D + 3D)
  • General linear multi-point constraints (u_slave = sum coeff * u_master, chains resolved) and rigid_link (full rigid-body coupling with lever arm), 2D + 3D
  • Rigid diaphragm — 2D (identity alias) and 3D (true affine constraint with lever-arm coupling, not identity aliasing)

wasm / browser boundary

  • CarapaceInputV1 wire format: input decoding plus a stepped Session/advance API, budget-driven for cooperative cancellation
  • wasm_bindgen boundary exposing decodeInput/advance as JS objects and arrays via serde-wasm-bindgen, with generated TypeScript definitions
  • Zero-copy transferable-typed-array wire format for postMessage (the wasm→JS leg still converts to JS objects; the analysis-worker→storage-worker leg, entirely on the pysees side, is already a transferred ArrayBuffer)
  • Configurable TangentStrategy/LineSearch/KrylovNewton through CarapaceInputV1, for static and transient stages, using tagged algorithm objects. Transient stages default to linear analysis when algorithm is omitted. See the JS solver configuration guide.
  • Arc-length stages (integrator: { kind: "arcLength", ... }) and combined convergence through CarapaceInputV1, with per-step continuation diagnostics in StepOutcome.continuation

Results / persistence — see docs/results-storage-indexeddb.md for the full design (IndexedDB-based, implemented on the pysees side, not SQLite/OPFS)

  • Bounded, per-advance()-call recorder batches — the solver never retains a whole-run history in memory
  • IndexedDB storage worker: dense run-wide row layout, idempotent writes, interrupted-run reconciliation on reload
  • Analysis-worker ↔ storage-worker MessageChannel wiring with backpressure (bounded in-flight-byte budget)
  • Engine recorders: node displacement, velocity/acceleration, reactions, element local forces, and fiber stress/strain (2D + 3D)
  • Engine modal frequencies/shape components and transient time histories through per-call recorder batches
  • pysees compiler/storage integration for recorders beyond displacement, modal/transient stages, and spatial models. The engine supports these; the frontend compiler currently emits planar static displacement runs only.
  • Results UI beyond a minimal debug panel — paged queries work, but nothing yet consumes them for real plots or deformed-shape scrubbing
  • Saved-run browsing, delete, and export UI (deleteRun and queryResults backend calls exist; export and a run-management UI remain unimplemented)

Superseded planning docs (the original milestone-by-milestone implementation plan, the 3D-profile architecture rationale, the pysees handoff contract, algorithm and friction derivations, and a rejected Turso storage alternative) live in docs/obsolete/ — useful for the reasoning behind past decisions, no longer maintained as living specs.

Workspace layout

carapace/
├── core/           # carapace-core: all FE logic. No wasm-bindgen dependency —
│                    compiles and tests identically on native and wasm32 targets.
├── wasm-bridge/     # carapace-wasm: input decoding, session API, and JS adapter
└── docs/
    ├── architecture.md             # domain model, element interface, how to extend
    ├── algorithms.md               # current solver API and JS configuration
    ├── arclength.md                # arc-length continuation design record
    ├── results-storage-indexeddb.md # current results-persistence design (pysees side)
    └── obsolete/                    # superseded planning docs, kept for history

Building

# native (fast iteration, real debuggers, no browser/Node round-trip)
cargo test --workspace

# one-time tool setup (wasm-bindgen must match Cargo.lock)
cargo install wasm-bindgen-cli --version 0.2.128 --locked

# build the shipped wasm: JS glue, TypeScript types and binary in pkg/ (loads under Node and browsers)
scripts/build-wasm.sh

# check the generated package through its application API (Node 22.18+)
node wasm-bridge/tests/boundary-smoke.ts

# check the generated declarations and their use by the smoke test
npm exec --package=typescript -- tsc --strict --noEmit --target es2022 --lib esnext,dom \
  --module nodenext wasm-bridge/tests/boundary-smoke.ts

The build is --target web (ES modules). A browser or worker initializes the module with the default init() export before calling decodeInput; under Node, comparison/load_wasm.mjs loads the same files with initSync.

Always verify a change on the native target first, wasm32 second — it isolates logic bugs from platform/toolchain bugs faster than debugging inside a wasm runtime directly.

scripts/build-wasm.sh generates everything in pkg/, including carapace_wasm.d.ts. Do not edit generated files. The application interface is decodeInput(input) → WasmSession, with advance(stepBudget) → StepOutcome and currentStageId() methods.

Wire-format types in wasm-bridge/src/input_v1/ derive tsify::Tsify alongside Serde, so fields, camelCase names, and tagged enums generate TypeScript definitions from the Rust source. The adapter uses Ts<T> to connect those definitions to function signatures, with explicit fallible conversion inside each call. Malformed inputs still throw an error, and decode errors retain their structured { kind, ... } shape (also exported as DecodeError). The current payload uses ordinary JS objects and arrays; these declarations do not describe the planned transferable typed-array format.

The full model/input format (tables, materials, stages, recorders, results, and errors) is documented in docs/input-format.md.

When extending the engine, update the Rust input types and decoder as needed, then rebuild to regenerate the JS types. A new binding is only needed for a new operation exposed to JavaScript; adding a material or solver behind the existing session interface does not require one.

Read next

Start with the Features checklist above for what's implemented today.

For the frontend/worker boundary and results persistence, read docs/results-storage-indexeddb.md — the current run lifecycle, transport, and IndexedDB results-storage design (implemented on the pysees side).

For implemented solver settings, JS examples, and remaining event-to-event stepping design, read docs/algorithms.md.

For historical design rationale — the original milestone plan, the 3D-profile architecture decisions, the pysees handoff contract, friction and algorithm design rationale, and a rejected Turso-based storage alternative — see docs/obsolete/. These are no longer maintained as living specs; treat them as an explanation of why, not a current what.

About

A thin, curated, WebAssembly-ready finite element analysis engine, written in Rust, covering the subset of structural element formulations, material models, and analysis types commonly used for buildings

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages