Resources Numerical accuracy Calculation policy

Steel member checks on a solved frame

Runs the steel code checks along every member for every strength combination, finds the governing ratio, checks the deflection limit, and finds what gives way first.

How the precision is kept

Demands are the largest station values of the member diagrams, in 64-bit numbers, divided by the code module's strength. The stations and the first-order analysis set the accuracy. Thresholds that decide whether a member carries compression or bending at all are absolute values in N and N·mm.

Calculations under this policy

12 calculations in 2 source files. 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-design.js CadCraft 0.8.2 64-bit 9 calculations Reviewed

Tests bin/test-frame-design.mjs

Yield and tensile strength of the steel

Exact

The fy and fu used for a member from its grade and thickness.

table value; EN: the lower pair when the thickest part is over 40 mm

A table lookup with no arithmetic. The values are whole MPa as the standards print them; the code's comment notes that 345 MPa stands for 50 ksi and 250 MPa for 36 ksi.

  • frame/frame-design.js
  • lines 37 to 41
  • CadCraft 0.8.2
export function gradeProps(code, name, tmax = 0) {
  const g = GRADES[code]?.[name];
  if (!g) return null;
  return code === 'en' && tmax > 40 ? { fy: g.fy40, fu: g.fu40 } : { fy: g.fy, fu: g.fu };      // EN 10025-2 Table 7: 16–40 mm / 40–80 mm
}

A diagram value between stations

64-bit

Reads a force or moment at any distance along the member from the station values.

a = aₖ + (x − xₖ) ÷ (xₖ₊₁ − xₖ) × (aₖ₊₁ − aₖ)

Straight-line interpolation between two stations; a curved diagram between stations is read as a chord. A zero station spacing is divided by 1 instead.

  • frame/frame-design.js
  • lines 77 to 85
  • CadCraft 0.8.2
const at = (x, a, xq) => {                                   // linear interpolation of a series
  if (xq <= x[0]) return a[0];
  const n = x.length - 1;
  if (xq >= x[n]) return a[n];
  let k = 0;
  while (x[k + 1] < xq) k++;
  const f = (xq - x[k]) / (x[k + 1] - x[k] || 1);
  return a[k] + f * (a[k + 1] - a[k]);
};

The diagram on one unbraced segment

64-bit

The stations inside a segment plus interpolated values at its two ends.

A station belongs to the segment when it is more than 1e-9 mm inside both ends, an absolute limit.

  • frame/frame-design.js
  • lines 86 to 93
  • CadCraft 0.8.2
/** The stations inside [x0, x1] plus both ends, for the named series. */
function segment(d, x0, x1, keys) {
  const idx = d.x.map((v, k) => k).filter((k) => d.x[k] > x0 + 1e-9 && d.x[k] < x1 - 1e-9);
  const xs = [x0, ...idx.map((k) => d.x[k]), x1];
  const out = { x: xs };
  for (const key of keys) out[key] = [at(d.x, d[key], x0), ...idx.map((k) => d[key][k]), at(d.x, d[key], x1)];
  return out;
}

Quarter-point moments of a segment

64-bit

The largest moment of a segment and the moments at its quarter, middle and three-quarter points.

|M| at x₀ + 0.25 L, 0.5 L, 0.75 L; max |M| over the stations

The three points are read by straight-line interpolation and the maximum is the largest station value.

  • frame/frame-design.js
  • lines 94 to 98
  • CadCraft 0.8.2
const quart = (x, M) => {                                    // |M| at the quarter points of a segment, and its maximum
  const L = x[x.length - 1] - x[0];
  const q = (f) => Math.abs(at(x, M, x[0] + f * L));
  return { max: Math.max(...M.map(Math.abs)), a: q(0.25), b: q(0.5), c: q(0.75) };
};

Buckling lengths and segments of a member

64-bit

The unbraced length, the number of segments and the effective lengths used in the checks.

lb = min(L, override or ratio × L); segments = ⌈L ÷ lb − 1e-9⌉; Lcy = ky L; Lcz = kz lb; Lt = lb

Because 1e-9 is taken off before rounding up, a quotient that rounding leaves a hair above a whole number does not add a segment. The last segment ends at L exactly.

  • frame/frame-design.js
  • lines 103 to 121
  • CadCraft 0.8.2
/** Everything one member needs from its section, steel and bracing, before any load. */
function memberSetup(model, prep, k, design, E, G) {
  const m = model.members[k];
  const sec = model.sections.find((s) => s.id === m.section);
  const ov = design.overrides[m.id] || {};
  const base = { id: m.id, ref: m.from?.ref || '', section: sec?.name || m.section };
  const shape = shapeOf(sec);
  if (!shape) return { ...base, skip: 'no-shape' };
  if (shape.welded) return { ...base, skip: 'welded' };
  const grade = ov.grade || design.grade;
  const g = gradeProps(design.code, grade, Math.max(shape.tf, shape.tw));
  if (!g) return { ...base, skip: 'no-grade' };
  const L = prep.mem[k].ax.L;
  const lb = Math.min(L, ov.lb ?? design.lbRatio * L);
  const nseg = Math.max(1, Math.ceil(L / lb - 1e-9));
  const bounds = Array.from({ length: nseg + 1 }, (_, i) => Math.min(L, i * lb));
  const ky = ov.ky ?? design.ky, kz = ov.kz ?? design.kz;
  return { ...base, shape, sec, L, lb, bounds, ky, kz, grade, fy: g.fy, fu: g.fu, E, G, Lcy: ky * L, Lcz: kz * lb, Lt: lb };
}

Demands and ratios, AISC 360

64-bit

Collects each check's demand, capacity and ratio for one member and combination.

ratio = demand ÷ capacity; demand = largest station value

A capacity of 0 or less gives an infinite ratio. Compression counts when the axial force is below −1 N and bending when a moment exceeds 1000 N·mm; both limits are absolute. The interaction ratio is evaluated at every station of the segment and the largest kept.

  • frame/frame-design.js
  • lines 123 to 158
  • CadCraft 0.8.2
function checkAISC(S, d, R, design) {
  const p = AISC.props(S.shape, S.sec);
  const sl = AISC.slender(p, S.fy, S.E);
  const skipComp = sl.compression && Math.min(...d.N) < -1;       // E7 not built: the compression checks are left out and the member is "partly checked"
  if (sl.bending && Math.max(maxAbs(d.My), maxAbs(d.Mz)) > 1000) return { skip: sl.bending.why, clause: 'AISC 360-16 §' + sl.bending.clause };
  const out = [];
  const add = (id, clause, demand, capacity, unit, x = null) => out.push({ id, clause, demand, capacity, ratio: capacity > 0 ? demand / capacity : Infinity, unit, x });
  const Nmax = Math.max(...d.N), Nmin = Math.min(...d.N);
  const t = AISC.tension(p, S.fy, S.fu);
  if (Nmax > 0) add('tension', 'AISC 360-16 §' + t.clause, Nmax, t.phiPn, 'N');
  const c = AISC.compression(p, S.fy, S.E, S.G, S.Lcy, S.Lcz, S.Lt);
  if (Nmin < 0 && !skipComp) add('compression', 'AISC 360-16 §E3' + (c.mode === 't' ? ', E4' : ''), -Nmin, c.phiPn, 'N');
  const minor = AISC.flexureMinor(p, S.fy, S.E);
  const Vmaj = AISC.shearMajor(p, S.fy, S.E), Vmin = AISC.shearMinor(p, S.fy, S.E);
  add('shear-z', 'AISC 360-16 §G2.1', maxAbs(d.Vz), Vmaj.phiVn, 'N');
  add('shear-y', 'AISC 360-16 §G4', maxAbs(d.Vy), Vmin.phiVn, 'N');
  add('bending-z', 'AISC 360-16 §F6', maxAbs(d.Mz), minor.phiMn, 'N·mm');
  const segs = [];
  for (let s = 0; s + 1 < S.bounds.length; s++) {
    const sg = segment(d, S.bounds[s], S.bounds[s + 1], ['My', 'Mz', 'N']);
    const Cb = AISC.cb(quart(sg.x, sg.My));
    const fl = AISC.flexureMajor(p, S.fy, S.E, S.bounds[s + 1] - S.bounds[s], Cb);
    const My = maxAbs(sg.My);
    add('bending-y', 'AISC 360-16 §F2' + (fl.flb ? ', F3' : ''), My, fl.phiMn, 'N·mm', sg.x[0]);
    let worst = { ratio: -1 };
    sg.x.forEach((x, k) => {
      const N = sg.N[k], Pc = N < 0 ? c.phiPn : t.phiPn;
      if (N < 0 && skipComp) return;
      const h = AISC.interaction(Math.abs(N), Pc, Math.abs(sg.My[k]), fl.phiMn, Math.abs(sg.Mz[k]), minor.phiMn);
      if (h.ratio > worst.ratio) worst = { ...h, x };
    });
    if (worst.ratio >= 0) add('interaction', 'AISC 360-16 §' + worst.eq, worst.ratio, 1, '', worst.x);
    segs.push({ Cb, Lb: S.bounds[s + 1] - S.bounds[s], Lp: fl.Lp, Lr: fl.Lr, zone: fl.zone, phiMn: fl.phiMn });
  }
  return { checks: out, partial: skipComp ? [sl.compression.why] : [], info: { Zx: p.Zx, Zy: p.Zy, phiPnc: c.phiPn, mode: c.mode, KLry: c.KLry, KLrz: c.KLrz, segs, Fy: S.fy } };
}

Demands and ratios, EN 1993

64-bit

Collects each check's demand, resistance and ratio for one member and combination.

ratio = demand ÷ resistance; demand = largest station value

The same absolute limits of 1 N and 1000 N·mm decide whether compression and bending act. The factor C1 passed to the lateral-torsional check is the quarter-point expression of AISC F1-1.

  • frame/frame-design.js
  • lines 160 to 218
  • CadCraft 0.8.2
function checkEN(S, d, R, design, hasPoint) {
  const na = design.na.steel;
  const p = EN.props(S.shape, S.sec);
  const cl = EN.classify(p, S.fy);
  const hasN = Math.min(...d.N) < -1, hasM = Math.max(maxAbs(d.My), maxAbs(d.Mz)) > 1000;
  if (hasM && cl.bending === 4) return { skip: 'class-4', clause: 'EN 1993-1-1 Table 5.2' };
  const skipComp = hasN && cl.compression === 4;                  // no effective-section method: compression left out, "partly checked"
  const cls = Math.max(hasM ? cl.bending : 1, hasN && !skipComp ? cl.compression : 1);
  const out = [];
  const add = (id, clause, demand, capacity, unit, x = null) => out.push({ id, clause, demand, capacity, ratio: capacity > 0 ? demand / capacity : Infinity, unit, x });
  const Nmax = Math.max(...d.N), Nmin = Math.min(...d.N);
  const E = S.E, G = S.G;
  const t = EN.tension(p, S.fy, S.fu, na);
  if (Nmax > 0) add('tension', 'EN 1993-1-1 §' + t.clause, Nmax, t.NtRd, 'N');
  const cs = EN.compressionCS(p, S.fy, na);
  const bk = EN.buckling(p, S.fy, E, G, S.Lcy, S.Lcz, S.Lt, na);
  if (Nmin < 0 && !skipComp) {
    add('compression-section', 'EN 1993-1-1 §6.2.4', -Nmin, cs.NcRd, 'N');
    add('compression', 'EN 1993-1-1 §6.3.1' + (bk.mode === 't' ? '.4' : '.1'), -Nmin, bk.NbRd, 'N');
  }
  const sz = EN.shearMajor(p, S.fy, na), sy = EN.shearMinor(p, S.fy, na);
  const Vz = maxAbs(d.Vz);
  add('shear-z', sz.buckling ? 'EN 1993-1-5 §5.2' : 'EN 1993-1-1 §6.2.6', Vz, sz.VRd, 'N');
  const partial = skipComp ? ['class-4'] : [];
  if (sz.buckling && Vz > 0.5 * sz.VbRd) partial.push('shear-buckling-interaction');      // EN 1993-1-5 §7.1 not built
  add('shear-y', 'EN 1993-1-1 §6.2.6', maxAbs(d.Vy), sy.VplRd, 'N');
  const bz = EN.bendingMinor(p, S.fy, na, cl.bending);
  add('bending-z', 'EN 1993-1-1 §6.2.5', maxAbs(d.Mz), bz.McRd, 'N·mm');
  const bv = EN.bendingWithShear(p, S.fy, na, cl.bending, Vz, sz.VplRd);
  const Wy = cls <= 2 ? p.Wply : p.Wely, Wz = cls <= 2 ? p.Wplz : p.Welz;
  const R2 = { NRk: p.A * S.fy, MyRk: Wy * S.fy, MzRk: Wz * S.fy };
  const segs = [];
  for (let s = 0; s + 1 < S.bounds.length; s++) {
    const sg = segment(d, S.bounds[s], S.bounds[s + 1], ['My', 'Mz', 'N']);
    const Lb = S.bounds[s + 1] - S.bounds[s];
    const C1 = AISCCb(sg);
    const lt = EN.ltb(p, S.fy, E, G, Lb, C1, cl.bending, na);
    const My = maxAbs(sg.My);
    add('bending-section', 'EN 1993-1-1 §6.2.5' + (bv.rho ? ', 6.2.8' : ''), My, bv.McRd, 'N·mm', sg.x[0]);
    add('bending-y', 'EN 1993-1-1 §6.3.2.2', My, lt.MbRd, 'N·mm', sg.x[0]);
    const cmY = EN.cm(sg.x, sg.My, hasPoint), cmZ = EN.cm(sg.x, sg.Mz, hasPoint);
    let wCS = { ratio: -1 }, wI = { r: -1 };
    sg.x.forEach((x, k) => {
      const N = sg.N[k], My_ = Math.abs(sg.My[k]), Mz_ = Math.abs(sg.Mz[k]);
      if (N < 0 && skipComp) return;
      const ma = EN.momentAxial(p, S.fy, na, cls, N, My_, Mz_);
      if (ma.ratio > wCS.ratio) wCS = { ...ma, x };
      if (N < 0) {
        const it = EN.interaction({ NEd: -N, My: My_, Mz: Mz_ }, R2, bk, lt, { y: cmY, z: cmZ, LT: cmY }, cls, na, !design.torsionFree);
        const r = Math.max(it.r1, it.r2);
        if (r > wI.r) wI = { r, x, which: it.r1 >= it.r2 ? '6.61' : '6.62' };
      }
    });
    if (wCS.ratio >= 0) add('moment-axial', 'EN 1993-1-1 §6.2.9' + (cls === 3 ? '.2' : '.1'), wCS.ratio, 1, '', wCS.x);
    if (wI.r >= 0) add('interaction', 'EN 1993-1-1 §6.3.3 (' + wI.which + ')', wI.r, 1, '', wI.x);
    segs.push({ Lb, C1, chiLT: lt.chiLT, Mcr: lt.Mcr, cmY, cmZ });
  }
  return { checks: out, partial, info: { class: cls, classes: cl, Wpl: p.Wply, chi: bk.chi, mode: bk.mode, curves: bk.curves, segs, fy: S.fy, NbRd: bk.NbRd } };
}

Elastic moduli used in the checks

64-bit

Chooses E and G for the capacity formulas.

EN: E = 210 000, G = 81 000 MPa; AISC: the model's steel

The analysis keeps the model's own modulus. A note is raised when the two differ by more than 0.5 % of the design value.

  • frame/frame-design.js
  • lines 231 to 234
  • CadCraft 0.8.2
const mat = model.materials[0] || { E: 200000, G: 77000 };
// the capacity formulas use the code's own moduli: EN 1993-1-1 §3.2.6 E = 210 000, G = 81 000 MPa; AISC 360 uses the model's steel
const E = D.code === 'en' ? 210000 : mat.E, G = D.code === 'en' ? 81000 : mat.G;
const modulus = Math.abs(mat.E - E) / E > 0.005 ? { model: mat.E, design: E } : null;

Deflection against the span limit

64-bit

The largest deflection off the chord over the service combinations, against L ÷ n.

ratio = deflection ÷ (L ÷ limit)

The deflection is the largest of 25 station values per combination.

  • frame/frame-design.js
  • lines 256 to 265
  • CadCraft 0.8.2
// serviceability: the deflection off the chord against the project's L/n limit
const defl = { set: '', ratio: 0, value: 0 };
for (const cb of service) {
  const R = sol.results['combo:' + cb.id];
  const d = diagram(prep, R, k, 24);
  const v = Math.max(...d.rel);
  const ratio = v / (S.L / D.deflLimit);
  if (ratio > defl.ratio) { defl.set = cb.id; defl.ratio = ratio; defl.value = v; defl.name = cb.name; }
}
row.defl = service.length ? { ...defl, limit: D.deflLimit, L: S.L } : null;
frame/frame-breakit.js CadCraft 0.8.2 64-bit 3 calculations Reviewed

Tests None found by the review

A combination with its loads scaled

64-bit

Makes a copy of the model whose governing combination has every factor multiplied by a load factor.

f′ = f × λ

One multiplication per factor; nothing else in the model is changed.

  • frame/frame-breakit.js
  • lines 26 to 29
  • CadCraft 0.8.2
const comboScaled = (model, comboId, lam) => ({
  ...model,
  combos: model.combos.filter((c) => c.id === comboId).map((c) => ({ ...c, factors: c.factors.map((x) => ({ ...x, f: x.f * lam })) })),
});

Load factor at first failure

64-bit

The factor on the governing combination at which the first member reaches a utilization of 1.

largest utilization(λ) = 1, found by solving the frame again for each λ

Each trial is a full solve and check, so the result has the accuracy of the search, which is done by the reserve function of study/breakit.js passed in as B, with an upper bound of 16 on the factor. A trial that cannot be solved counts as failed.

  • frame/frame-breakit.js
  • lines 60 to 70
  • CadCraft 0.8.2
// 3. the reserve on the governing combination: scale it until the first member reaches 1
let reserve = null;
if (gov) {
  let n = 0;
  reserve = await B.reserve(async (lam) => {
    onProgress(++n);
    const m2 = comboScaled(model, gov.id, lam), s2 = solveFrame(m2, { sets: [{ id: gov.id, kind: 'combo' }] });
    if (!s2.ok) return Infinity;
    const c2 = checkFrame(m2, s2, design);
    return c2.ok ? c2.max : Infinity;
  }, { cancelled, max: 16 });

Connection utilization

64-bit

The shear and axial force at a member end against the resistance the app states for the joint.

V = √(Vy² + Vz²); utilization = max(V ÷ shear, |N| ÷ axial)

The resultant shear is one Math.hypot of the two end shears read from the Float64Array of end forces. In the finding that is shown, forces are rounded to whole N and to 0.01 kN; the utilization itself is not rounded.

  • frame/frame-breakit.js
  • lines 77 to 102
  • CadCraft 0.8.2
// 4. connections: member end forces of every strength combination against the resistance the app states
const notChecked = [];
const conn = [];
const strength = model.combos.filter((c) => c.limit === 'strength' && sol.results['combo:' + c.id]);
const idx = new Map(model.members.map((m, k) => [m.id, k]));
for (const c of connections) {
  if (!(c.shear > 0)) { notChecked.push(c.id); continue; }
  let worst = { util: 0, set: null, V: 0, N: 0 };
  for (const cb of strength) {
    const R = sol.results['combo:' + cb.id];
    for (const e of c.ends) {
      const k = idx.get(e.member);
      if (k === undefined) continue;
      const o = 12 * k + (e.end === 'j' ? 6 : 0);
      const V = Math.hypot(R.ends[o + 1], R.ends[o + 2]), N = Math.abs(R.ends[o]);
      const u = Math.max(V / c.shear, c.axial > 0 ? N / c.axial : 0);
      if (u > worst.util) worst = { util: u, set: cb.id, V, N };
    }
  }
  conn.push({ id: c.id, label: c.label || c.id, ...worst, shear: c.shear });
}
conn.sort((a, b) => b.util - a.util);
conn.forEach((c, i) => {
  if (i > 0 && c.util <= 1) return;
  findings.push({ id: 'connection:' + c.id, kind: 'connection', subject: { kind: 'connection', id: c.id, label: c.label }, util: c.util, factor: c.util > 0 ? 1 / c.util : null, failing: c.util > 1,
    what: { set: c.set, shearKN: Math.round(c.V / 10) / 100, resistKN: Math.round(c.shear / 10) / 100 }, req: { op: '<=', value: Math.round(c.shear), unit: 'N' }, found: { value: Math.round(c.V), unit: 'N' } });

In detail

Method
Largest demand over 49 stations per member and combination; unbraced segments of equal length; utilization = demand ÷ capacity; deflection off the chord against L ÷ n. Break it scales the governing combination's factors and solves again.
Number types
64-bit numbers.
Tolerances
A member is in compression when its axial force is below −1 N and in bending when a moment exceeds 1000 N·mm (absolute). Segment bounds use 1e-9 mm. A modulus note is raised when the model's E differs from the code's by more than 0.5 %.
Limits
First-order forces. For EN 1993 the checks use E = 210 000 and G = 81 000 MPa whatever the model's steel. Yield strengths are the table values in MPa (345 MPa for 50 ksi steel, as the code's comment notes).

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach