@jtgtools/xshell
v0.0.2
Published
TypeScript finite-element analysis engine for plates, flat shells, slabs, and mat foundations
Maintainers
Readme
xshell
A pure TypeScript flat-shell finite-element analysis engine for plates, shells, RC slabs, and mat foundations. It implements MITC4 quadrilaterals and DKT+Allman triangles with sparse direct and iterative solvers.
xshell is deliberately design-code-agnostic. It computes displacements, reactions, M/Q/N resultants, and stresses; callers supply stiffness modifiers and load-combination factors appropriate to their own design standard.
Highlights
- MITC4 and DKT+Allman flat-shell elements, including mixed meshes
- Isotropic and rotated orthotropic materials
- Nodal, pressure, surface-traction, line force/moment, body, and temperature actions
- Multiple load cases, support settlements, combinations, and packed response envelopes
- One shared stiffness assembly and factorization for every linear RHS
- Nodal springs, line springs, Winkler foundations, and compression-only contact
- Rigid links, general linear MPCs, and skew supports
- AMD-ordered sparse LDLᵀ with memory-gated PCG/ILU(0) fallback
- Web Worker dispatch in browsers
- Full Gauss-point and extrapolated nodal stress recovery
All public quantities use SI units. Node DOFs are ordered
[ux, uy, uz, rx, ry, rz] and rotations follow the right-hand rule.
Installation
npm install @jtgtools/xshellxshell is ESM-only and supports Node.js 20+ and modern browser bundlers.
Quick start
import { analyze, generateRectMesh } from '@jtgtools/xshell';
const material = { E: 30e9, nu: 0.2, rho: 2500 };
const { nodes, elements } = generateRectMesh(
8, 6, 32, 24, 'MITC4', material, 0.25
);
const boundaryConditions = [];
for (const node of nodes) {
const onEdge = node.x === 0 || node.y === 0 || node.x === 8 || node.y === 6;
if (onEdge) {
boundaryConditions.push({ nodeId: node.id, dof: 2, value: 0 });
}
}
boundaryConditions.push(
{ nodeId: 0, dof: 0, value: 0 },
{ nodeId: 0, dof: 1, value: 0 },
{ nodeId: 32, dof: 1, value: 0 },
);
const allElements = elements.map(element => element.id);
const result = await analyze({
model: { nodes, elements, boundaryConditions },
loadCases: [
{
id: 'DL',
name: 'Self weight',
loads: [
{ type: 'bodyAcceleration', acceleration: [0, 0, -9.80665] },
],
},
{
id: 'LL',
name: 'Live load',
loads: [
// Positive pressure follows each element's +e3 normal. For a normal
// XY mesh with +e3 upward, a negative value acts downward.
{ type: 'pressure', elementIds: allElements, value: -5_000 },
],
},
],
combinations: [
{ id: 'ULS-1', factors: { DL: 1.2, LL: 1.6 } },
{ id: 'SLS-1', factors: { DL: 1.0, LL: 1.0 } },
],
options: { solver: 'auto', resultDetail: 'full' },
}, {
onProgress: event => console.log(event.percent, event.stage, event.resultId),
});
const uls = result.combinations['ULS-1'];
console.log('center uz', uls.displacements[6 * 412 + 2]);
console.log('solver setup reused', result.metadata.numericFactorizations === 1);Analysis request
interface AnalysisRequest {
model: {
nodes: Node[];
elements: Element[];
boundaryConditions?: BoundaryCondition[];
constraints?: Constraint[];
};
loadCases: LoadCase[];
combinations?: LoadCombination[];
envelopes?: EnvelopeRequest[];
options?: AnalysisOptions;
}Load-case and combination IDs must be unique. Combination factors may reference load cases only; nested combinations are intentionally unsupported. xshell always forms and solves the factored load vector, then performs fresh stress recovery— it never combines von Mises or principal-stress scalars.
For linear models, K is assembled and factored once. For compression-/tension-only supports, every case and combination gets an independent active-set solution because nonlinear contact states cannot be superposed.
Loads
// Global point force and moment
{ type: 'nodal', nodeId: 10, force: [0, 0, -10_000], moment: [0, 500, 0] }
// Uniform normal pressure [Pa], positive along each element's +e3
{ type: 'pressure', elementIds: [1, 2, 3], value: -5_000 }
// Uniform global surface traction [Pa]
{ type: 'surfaceTraction', elementIds: [1, 2, 3], traction: [1_000, 0, 0] }
// Uniform global line force [N/m] and optional line moment [N·m/m]
{ type: 'line', nodeIds: [10, 11, 12], forcePerLength: [0, 0, -2_000] }
// Body acceleration [m/s²]; elementIds omitted means every element
{ type: 'bodyAcceleration', acceleration: [0, 0, -9.80665] }
// Top/bottom temperature changes [K or °C difference]
{ type: 'temperature', elementIds: [1, 2, 3], deltaTTop: 20, deltaTBottom: -5 }Line-load chains must follow real mesh edges. Body acceleration requires material.rho
on every targeted element. Temperature actions require alpha for isotropic materials
or alpha1/alpha2 for orthotropic materials. Uniform temperature produces membrane
strain; a top/bottom difference produces thermal curvature. Thermal equivalent forces
and mechanical stress recovery use the same constitutive matrices and stiffness modifiers.
Validation reports the load case and load ID/index before assembly begins.
Case-specific support settlement
A load case may add a prescribed settlement to a DOF that is already prescribed by the model:
{
id: 'SETTLEMENT',
loads: [],
settlements: [{ nodeId: 42, dof: 2, value: -0.01 }],
}The case value is added to the model BC value. Combination factors apply to the case addition, while the model BC value is included once. The constrained DOF set therefore stays unchanged and the same factorization remains reusable.
Response envelopes
envelopes: [{
id: 'ULS-envelope',
sourceIds: ['ULS-1', 'ULS-2', 'ULS-3'],
modes: ['min', 'max', 'absMax'],
}]Envelopes contain packed Float64Array values and Uint32Array governing-source
indices. They cover displacements, reactions, and—when element recovery is requested—
Mx, My, Mxy, Qx, Qy, Nx, Ny, and Nxy at every Gauss point. Packed arrays
avoid millions of per-scalar JavaScript objects on large combination sets. Use
envelope.sourceIds[index] to recover the governing case or combination ID.
Result detail and memory
Each case and combination returns a CaseResult. Use resultDetail to control
recovery cost and memory:
'displacements': displacements and reactions'elements': also Gauss-point element results'full'(default): also extrapolated nodal results
Batch metadata reports stiffness assemblies, symbolic analyses, numeric
factorizations, preconditioner builds, RHS solves, and whether execution actually
occurred inline or in a worker, so reuse and dispatch are observable.
Runtime controls
Callbacks and cancellation are separate from the serializable request:
const controller = new AbortController();
const promise = analyze(request, {
signal: controller.signal,
onProgress: event => console.log(event),
});
controller.abort();In a browser worker, aborting terminates the worker immediately. Inline analyses check cancellation between major stages, results, and contact iterations.
Typed errors
Every operational failure derives from XShellError and carries a stable code:
ValidationError—E_VALIDATIONSolverError—E_SOLVERSingularityError—E_SINGULARITYAnalysisAbortedError—E_ABORTEDWorkerError—E_WORKERInternalError—E_INTERNAL
Error classes and structured context survive worker boundaries, so consumers can use
instanceof or error.code rather than parsing messages.
A SingularityError additionally exposes nodeId, dof, dofName, pivotValue,
hint, and diagnostic. For models up to 50,000 algebraic DOFs, failure diagnostics
extract a bounded near-null mode and report its dominant physical node/DOF entries plus
rigid-body participation. Disable the extra failure-path work with
options.diagnoseSingularity: false, or change the bound with
singularityDiagnosticDofLimit.
Opt-in stiffness scaling statistics are available without changing the solution:
const result = await analyze({
...request,
options: { ...request.options, report: { conditioning: true } },
});
console.log(result.metadata.conditioning?.diagonalRatio);API and result-schema stability
Releases follow semantic versioning. Removing or renaming exports; changing DOF order, sign conventions, units, validation severity, error codes, or existing result-field meaning is breaking. New optional inputs, metadata, warnings, and accuracy improvements are minor changes.
AnalysisResult.metadata.schemaVersion versions the stored result shape independently
of package semver. metadata.specVersion identifies the engine convention specification.
Numerical digits are governed by documented benchmark tolerances rather than semver.
Development
npm run verify # typecheck, build, tests, numerical accuracy, error audit
npm run benchmark:assert # isolated accuracy + absolute performance gates
npm run smoke:package # npm-pack + fresh-project typecheck and analysis
npm run smoke:browser # packaged Vite app; asserts real Web Worker executionLicense
MIT
