Resources Numerical accuracy Calculation policy

3D frame analysis

Member forces, reactions and deflections of a 3D frame by the direct stiffness method.

How the precision is kept

Every matrix and vector is a 64-bit array. The stiffness matrix is factorised once by banded Cholesky; a pivot that vanishes stops the run and names the node and direction instead of being divided by. The tests compare every textbook case with a second, independent implementation and check that loads and reactions balance.

Calculations under this policy

4 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

frame/frame-solve.js DetailCraft 0.8.1 64-bit 4 calculations Reviewed

Tests bin/test-frame.mjs

Member stiffness matrix

64-bit

The 12 by 12 stiffness of one beam-column in its own axes.

EA ÷ L, GJ ÷ L, 12EI ÷ L³, 6EI ÷ L², 4EI ÷ L, 2EI ÷ L

Closed-form terms written straight into a 64-bit array. Symmetry is by construction: each term is stored on both sides of the diagonal.

  • frame/frame-solve.js
  • lines 46 to 62
  • DetailCraft 0.8.1
/** 12×12 local stiffness (row-major Float64Array). */
export function localK(E, G, A, Iy, Iz, J, L) {
  const k = new Float64Array(144);
  const s = (i, j, v) => { k[i * 12 + j] += v; if (i !== j) k[j * 12 + i] += v; };
  const ea = E * A / L, gj = G * J / L;
  s(0, 0, ea); s(0, 6, -ea); s(6, 6, ea);
  s(3, 3, gj); s(3, 9, -gj); s(9, 9, gj);
  const z1 = 12 * E * Iz / L ** 3, z2 = 6 * E * Iz / L ** 2, z3 = 4 * E * Iz / L, z4 = 2 * E * Iz / L;
  s(1, 1, z1); s(1, 5, z2); s(1, 7, -z1); s(1, 11, z2);
  s(5, 5, z3); s(5, 7, -z2); s(5, 11, z4);
  s(7, 7, z1); s(7, 11, -z2); s(11, 11, z3);
  const y1 = 12 * E * Iy / L ** 3, y2 = 6 * E * Iy / L ** 2, y3 = 4 * E * Iy / L, y4 = 2 * E * Iy / L;
  s(2, 2, y1); s(2, 4, -y2); s(2, 8, -y1); s(2, 10, -y2);
  s(4, 4, y3); s(4, 8, y2); s(4, 10, y4);
  s(8, 8, y1); s(8, 10, y2); s(10, 10, y3);
  return k;
}

Equivalent nodal loads

64-bit

A load along a member as forces and moments at its two ends.

p = ∫ N(ξ) · w(ξ) dx, by 4-point Gauss-Legendre quadrature

Exact for uniform and linearly varying loads: the integrand is a polynomial of degree 4 at most, and four Gauss points integrate degree 7 exactly. Only rounding remains.

  • frame/frame-solve.js
  • lines 64 to 78
  • DetailCraft 0.8.1
/** Equivalent nodal loads (local, 12) of one load piece on a member of length L. */
export function equivalentLoads(piece, L, p = new Float64Array(12)) {
  const add = (xi, q) => {      // q = force (per length × weight, or a point force) at ξ along the member
    const N1 = 1 - 3 * xi * xi + 2 * xi ** 3, N2 = L * (xi - 2 * xi * xi + xi ** 3), N3 = 3 * xi * xi - 2 * xi ** 3, N4 = L * (-xi * xi + xi ** 3);
    if (piece.comp === 0) { p[0] += q * (1 - xi); p[6] += q * xi; }
    else if (piece.comp === 1) { p[1] += q * N1; p[5] += q * N2; p[7] += q * N3; p[11] += q * N4; }
    else { p[2] += q * N1; p[4] -= q * N2; p[8] += q * N3; p[10] -= q * N4; }
  };
  if (piece.kind === 'point') add(piece.a / L, piece.P);
  else {
    const { a, b, w1, w2 } = piece, h = (b - a) / 2;
    for (const [g, wt] of GAUSS4) { const x = a + h * (g + 1); add(x / L, (w1 + (w2 - w1) * (x - a) / (b - a)) * wt * h); }
  }
  return p;
}

Banded Cholesky factorisation

64-bit

Factorises the stiffness matrix once, for every load set.

K = L · Lᵀ; pivot s = k_jj − Σ l²; mechanism when s ≤ 1e-11 × k_jj

The pivot test is relative to the diagonal term it started from, so it does not depend on the units or the size of the model. A failed pivot is reported, never divided by.

  • frame/frame-solve.js
  • lines 274 to 290
  • DetailCraft 0.8.1
// banded Cholesky, in place: L(i, j) at i·B + (i − j)
for (let j = 0; j < n; j++) {
  const kjj = K[j * B];
  let s = kjj;
  for (let k = Math.max(0, j - band); k < j; k++) { const l = K[j * B + (j - k)]; s -= l * l; }
  if (!(s > PIVOT_TOL * Math.abs(kjj)) || !(kjj > 0)) {
    const q = dofOf[j];
    return { error: { code: 'mechanism', node: model.nodes[Math.floor(q / 6)].id, dof: q % 6 }, warnings };
  }
  const d = Math.sqrt(s);
  K[j * B] = d;
  for (let i = j + 1; i <= Math.min(n - 1, j + band); i++) {
    let v = K[i * B + (i - j)];
    for (let k = Math.max(0, i - band); k < j; k++) v -= K[i * B + (i - k)] * K[j * B + (j - k)];
    K[i * B + (i - j)] = v / d;
  }
}

Forward and back substitution

64-bit

Displacements for one load set from the factorised matrix.

L · y = f, then Lᵀ · u = y

Two passes over the band in 64-bit. No pivoting is needed: the matrix is symmetric positive definite once the factorisation has passed.

  • frame/frame-solve.js
  • lines 315 to 321
  • DetailCraft 0.8.1
function solveBand(prep, f) {
  const { n, band, B, L } = prep;
  const y = new Float64Array(n);
  for (let i = 0; i < n; i++) { let s = f[i]; for (let k = Math.max(0, i - band); k < i; k++) s -= L[i * B + (i - k)] * y[k]; y[i] = s / L[i * B]; }
  for (let i = n - 1; i >= 0; i--) { let s = y[i]; for (let k = i + 1; k <= Math.min(n - 1, i + band); k++) s -= L[k * B + (k - i)] * y[k]; y[i] = s / L[i * B]; }
  return y;
}

In detail

Method
Direct stiffness method with 2-node, 12-degree-of-freedom Euler-Bernoulli members (McGuire, Gallagher and Ziemian, Matrix Structural Analysis, 2nd ed.). Loads along a member become nodal loads by 4-point Gauss-Legendre quadrature, which is exact for uniform, linearly varying and point loads. Nodes are numbered by reverse Cuthill-McKee and the stiffness matrix is factorised once by banded Cholesky.
Number types
64-bit arrays for every matrix, load, displacement and reaction.
Tolerances
A pivot that falls to 1e-11 of its own diagonal term, or below zero, is a mechanism: the run stops and names the node and direction.
Checked by
The frame test compares every textbook case with a second implementation of the same method written in CraftScript, and checks that loads and reactions balance.
Limits
Linear static analysis: no shear deformation, no warping, small displacements. Deflection along a member is integrated on a fine grid, with an error far below 0.01%. It is an analysis aid, not a code check.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach