Resources Numerical accuracy Calculation policy

Settlement, flexible mats, pile groups and retaining walls

Estimates elastic settlement, analyzes a footing as a plate on springs, shares a load over a pile group and checks a cantilever retaining wall.

How the precision is kept

64-bit arithmetic. Settlement, pile reactions and the retaining wall are closed-form expressions. The flexible mat is a finite-element plate on springs solved by the shared frame library, so its results depend on the mesh size, which is chosen between 100 and 600 mm and never finer than about 8000 nodes allow.

Calculations under this policy

11 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.

foundation.js DetailCraft 0.8.1 64-bit 11 calculations Reviewed

Tests tools/detailcraft-test/test-foundation.mjs, tools/detailcraft-test/test-foundation-3.mjs

Settlement at the corner of a loaded rectangle

64-bit

The settlement under the corner of a flexible rectangle carrying a uniform pressure on an elastic half-space.

w = q·b·(1 − ν²) ÷ (π·E) × (m·ln((1 + √(m² + 1)) ÷ m) + ln(m + √(m² + 1))); m = long side ÷ short side

One expression with Math.sqrt, Math.log and Math.PI. m is at least 1, so both logarithms have arguments above 1.

  • foundation.js
  • lines 323 to 327
  • DetailCraft 0.8.1
/** The settlement (mm) at the corner of a flexible rectangle b × l (m) carrying q (kPa) on an elastic half-space (E MPa, ν): Timoshenko and Goodier, m = l/b ≥ 1. */
const cornerSettlement = (b, l, q, E, nu) => {
  const m = Math.max(l, b) / Math.min(l, b), sh = Math.min(l, b), r = Math.sqrt(m * m + 1);
  return q * sh * (1 - nu * nu) / (Math.PI * E * 1000) * (m * Math.log((1 + r) / m) + Math.log(m + r)) * 1000;
};

Settlement at the center and corner of a footing

64-bit

The elastic settlement at the middle and at a corner of the footing.

center = 4 × corner of the quarter rectangle

Superposition of four quarter rectangles; sizes are divided by 1000 to meters first.

  • foundation.js
  • lines 328 to 337
  • DetailCraft 0.8.1
/**
 * Elastic settlement of a loaded L × B (mm) rectangle at pressure q (kPa) on a half-space with modulus Es (MPa) and Poisson's ratio nu: the centre
 * is four corners of the quarter rectangles, the corner is one corner of the whole. Flexible loading, so the centre is an upper bound for a stiff
 * footing's average; a layer of limited depth, the overburden relief and consolidation are left out (the soil is yours).
 */
export function settlement(L, B, q, Es, nu = 0.3) {
  const l = L / 1000, b = B / 1000;
  const corner = cornerSettlement(b, l, q, Es, nu), centre = 4 * cornerSettlement(b / 2, l / 2, q, Es, nu);
  return { centre, corner };
}

Mat mesh size and concrete stiffness

64-bit

The element size of the plate mesh and the concrete's modulus.

size = max(min(clamp(min(L, B) ÷ 14, 150, 600), max(100, smallest column ÷ 3)), √(L·B ÷ 8000)); E_c = 4700·√f′c

The mesh size sets the accuracy of everything the mat reports. The modulus uses the coefficient 4700 with the strength in MPa.

  • foundation.js
  • lines 358 to 361
  • DetailCraft 0.8.1
const smallest = Math.min(...withLoads.map((l) => Math.min(l.cx || 300, l.cy || 300)));
let size = opts.size || Math.min(Math.min(600, Math.max(150, Math.min(L, B) / 14)), Math.max(100, smallest / 3));
size = Math.max(size, Math.sqrt(L * B / 8000));
const Ec = 4700 * Math.sqrt(fc), nu = 0.2;

Soil springs under the nodes

64-bit

The spring stiffness under each node from the subgrade modulus and the area the node carries.

A = |d₁ × d₂| ÷ 2 (the two diagonals); k_node = k_s × 10⁻⁶ × Σ A ÷ 4

Each four-node element's area is half the cross product of its diagonals, shared equally between its nodes. The subgrade modulus goes from kN/m³ to N/mm³ by one multiplication by 1e-6.

  • foundation.js
  • lines 371 to 375
  • DetailCraft 0.8.1
  const A = Math.abs((q[2].x - q[0].x) * (q[3].y - q[1].y) - (q[3].x - q[1].x) * (q[2].y - q[0].y)) / 2;
  for (const i of p.nodes) trib.set(i, (trib.get(i) || 0) + A / 4);
}
const ks = soil.ks * 1e-6;   // kN/m³ → N/mm³
json.supports = json.nodes.map((n) => ({ node: n.id, fix: [true, true, false, false, false, true], k: [0, 0, ks * trib.get(n.id), 0, 0, 0] }));

A column load spread over its footprint

64-bit

The force on each node inside a column's footprint.

R_i = P ÷ n + M_y·dx_i ÷ Σdx² − M_x·dy_i ÷ Σdy²

The footprint is widened by 1 mm each way when nodes are picked. A moment share is left out when the sum of squares is 1e-12 m² or less; with one node only the axial part is applied.

  • foundation.js
  • lines 376 to 387
  • DetailCraft 0.8.1
// a column's load on the nodes inside its footprint: P/n plus the moment shares, as a pile group would take them
const spread = (l, set) => {
  const hw = (l.cx || 300) / 2 + 1, hh = (l.cy || 300) / 2 + 1;
  let at = json.nodes.filter((n) => Math.abs(n.x - l.x) <= hw && Math.abs(n.y - l.y) <= hh);
  if (!at.length) { let best = null; for (const n of json.nodes) { const d = Math.hypot(n.x - l.x, n.y - l.y); if (!best || d < best.d) best = { n, d }; } at = [best.n]; }
  const sx2 = at.reduce((a, n) => a + ((n.x - l.x) / 1000) ** 2, 0), sy2 = at.reduce((a, n) => a + ((n.y - l.y) / 1000) ** 2, 0);
  return at.map((n) => {
    const dx = (n.x - l.x) / 1000, dy = (n.y - l.y) / 1000;
    const R = (set.P || 0) / at.length + (sx2 > 1e-12 ? (set.My || 0) * dx / sx2 : 0) - (sy2 > 1e-12 ? (set.Mx || 0) * dy / sy2 : 0);   // kN, down; My loads the +x side more, Mx the −y side
    return { type: 'node', node: n.id, F: [0, 0, -R * 1000, 0, 0, 0] };
  });
};

Mat settlement and contact pressure

64-bit

The largest and smallest settlement of the mat and the largest spring pressure.

q = k_s × w ÷ 1000; reaction = Σ k_node × w ÷ 1000

Maximums over the node displacements returned by the frame solver. The total spring reaction is summed in node order.

  • foundation.js
  • lines 399 to 403
  • DetailCraft 0.8.1
let w = 0, q = 0, wMin = Infinity;
const a = new Map();
for (const n of json.nodes) { const k = idx.get(n.id), uz = -R.service.u[6 * k + 2]; w = Math.max(w, uz); wMin = Math.min(wMin, uz); q = Math.max(q, soil.ks * uz / 1000); a.set(n.id, uz); }
out.settleMax = w; out.settleMin = wMin; out.qMax = q;
out.reaction = json.nodes.reduce((t, n) => t + ks * trib.get(n.id) * a.get(n.id), 0) / 1000;   // kN: the springs' total, equal to the service loads

Mat bending moments and shear

64-bit

The largest sagging and hogging moments per meter each way and the largest transverse shear.

M = max and min of the node moments ÷ 1000; V = max √(q_x² + q_y²) away from the columns

Node values come from the frame library's plate results; this code only takes maximums. Shear is read only at nodes farther than d from every column footprint.

  • foundation.js
  • lines 410 to 420
  • DetailCraft 0.8.1
let mxP = 0, mxN = 0, myP = 0, myN = 0, qv = 0;
const dd = (H - f.mat.cover - BARS.get(f.mat.barX).db) ;
const nearColumn = (n) => withLoads.some((l) => Math.abs(n.x - l.x) < (l.cx || 300) / 2 + dd && Math.abs(n.y - l.y) < (l.cy || 300) / 2 + dd);
for (const n of json.nodes) {
  const v = nr.get(idx.get(n.id))?.[0]?.values || nr.get(idx.get(n.id))?.values;
  if (!v) continue;
  mxP = Math.max(mxP, v[3]); mxN = Math.min(mxN, v[3]); myP = Math.max(myP, v[4]); myN = Math.min(myN, v[4]);
  if (!nearColumn(n)) qv = Math.max(qv, Math.hypot(v[6], v[7]));   // shear is read at d from the column faces, not under the load
}
// N·mm/mm → kN·m/m; N/mm → kN/m
const M = { xP: mxP / 1000, xN: mxN / 1000, yP: myP / 1000, yN: myN / 1000, V: qv };

Hogging capacity of plain concrete

64-bit

The hogging moment a mat with no top bars can take.

capacity = 0.60 × 0.42·λ·√f′c × (H² ÷ 6) × 1000 ÷ 10⁶

Checked only when the hogging moment is more than 2 % of the larger of 1 kN·m/m and the sagging moment.

  • foundation.js
  • lines 433 to 435
  • DetailCraft 0.8.1
const hog = -lay.neg;
if (hog > 0.02 * Math.max(1, lay.pos)) {
  const cap = PHI_PLAIN * 0.42 * lam0(soil) * Math.sqrt(fc) * (H * H / 6) * 1000 / 1e6;   // kN·m/m

Spring pressure at a point

64-bit

The soil pressure at any point of the mat from the settlements of the nodes round it.

q = k_s ÷ 1000 × bilinear blend of the four corner settlements

The blend uses the element's bounding rectangle. A corner of that rectangle is matched to a node within 1e-6 mm; where no node is there, that corner counts as zero settlement, so on an element that is not an upright rectangle the pressure is read low.

  • foundation.js
  • lines 456 to 468
  • DetailCraft 0.8.1
/** The springs' pressure (kPa) at a point (mm): the bilinear field of the node settlements times ks. */
function interp(Rs, model, idx, json, x, y, ks) {
  // nodes lie on a grid-ish mesh: find the plate containing the point and interpolate by its bilinear shape in plate coordinates (a quad here is near rectangular)
  const byId = json._byId || (json._byId = new Map(json.nodes.map((n) => [n.id, n])));
  const hit = json._plates || (json._plates = json.plates.map((p) => { const q = p.nodes.map((i) => byId.get(i)); return { p, q, x0: Math.min(...q.map((n) => n.x)), x1: Math.max(...q.map((n) => n.x)), y0: Math.min(...q.map((n) => n.y)), y1: Math.max(...q.map((n) => n.y)) }; }));
  const e = hit.find((h) => x >= h.x0 - 1e-6 && x <= h.x1 + 1e-6 && y >= h.y0 - 1e-6 && y <= h.y1 + 1e-6);
  if (!e) return 0;
  const u = (n) => -Rs.u[6 * idx.get(n.id) + 2];
  const tx = e.x1 > e.x0 ? (x - e.x0) / (e.x1 - e.x0) : 0, ty = e.y1 > e.y0 ? (y - e.y0) / (e.y1 - e.y0) : 0;
  const at = (cx, cy) => e.q.find((n) => Math.abs(n.x - cx) < 1e-6 && Math.abs(n.y - cy) < 1e-6);
  const g = (cx, cy) => { const n = at(cx, cy); return n ? u(n) : 0; };
  return ks / 1000 * ((1 - tx) * (1 - ty) * g(e.x0, e.y0) + tx * (1 - ty) * g(e.x1, e.y0) + tx * ty * g(e.x1, e.y1) + (1 - tx) * ty * g(e.x0, e.y1));
}

Pile group reactions

64-bit

The load each pile takes under a vertical load and two moments.

R_i = P ÷ n + M_x·y_i ÷ Σy² + M_y·x_i ÷ Σx²

Positions are taken from the group's centroid, itself a mean of the pile coordinates. The sum of the reactions is returned with a flag that it equals P within 1e-6 of P.

  • foundation.js
  • lines 472 to 493
  • DetailCraft 0.8.1
/**
 * The elastic method: every pile takes P/n plus the moment shares, R_i = P/n + Mx·y_i/Σy² + My·x_i/Σx² (a rigid cap, equal piles, with x and y from
 * the group's centroid). `cap` = { compression, tension } in kN a pile may carry (yours: from the pile's design, not from here).
 */
export function pileGroup(piles, load, cap = {}) {
  const n = piles.length;
  const cx = piles.reduce((s, p) => s + p.x, 0) / n, cy = piles.reduce((s, p) => s + p.y, 0) / n;
  const xs = piles.map((p) => (p.x - cx) / 1000), ys = piles.map((p) => (p.y - cy) / 1000);
  const sx2 = xs.reduce((s, v) => s + v * v, 0), sy2 = ys.reduce((s, v) => s + v * v, 0);
  const P = load.P || 0, Mx = load.Mx || 0, My = load.My || 0;
  const reactions = piles.map((p, i) => P / n + (sy2 > 1e-12 ? Mx * ys[i] / sy2 : 0) + (sx2 > 1e-12 ? My * xs[i] / sx2 : 0));
  const checks = [];
  const top = Math.max(...reactions), bot = Math.min(...reactions);
  if (cap.compression > 0) checks.push(mk('pile-compression', 'pile group, elastic method', top, cap.compression, 'kN', { n, R: r1(top), cap: cap.compression, pile: reactions.indexOf(top) + 1 }));
  else checks.push(note('pile-compression', 'pile group, elastic method', { R: r1(top) }));
  if (bot < -1e-9) {
    if (cap.tension > 0) checks.push(mk('pile-tension', 'pile group, elastic method', -bot, cap.tension, 'kN', { R: r1(bot), cap: cap.tension, pile: reactions.indexOf(bot) + 1 }));
    else checks.push({ id: 'pile-tension', clause: 'pile group, elastic method', status: cap.tension === 0 ? 'fail' : 'note', unit: 'kN', demand: -bot, capacity: 0, ratio: Infinity, vars: { R: r1(bot), pile: reactions.indexOf(bot) + 1 } });
  }
  const sum = reactions.reduce((s, v) => s + v, 0);
  return { reactions, sum, centroid: [cx, cy], checks, equilibrium: Math.abs(sum - P) < 1e-6 * Math.max(1, Math.abs(P)) };
}

Cantilever retaining wall

64-bit

Overturning, sliding and bearing of a cantilever wall with level backfill.

K_a = tan²(45° − φ ÷ 2); P_a = ½·K_a·γ·H_t²; e = B ÷ 2 − (M_r − M_o) ÷ W

The angle is turned to radians with Math.PI. The eccentricity subtracts the overturning moment from the resisting moment, two numbers that come close together as the wall nears overturning. The base pressure reuses the footing's pressure plane on a 1 m strip.

  • foundation.js
  • lines 497 to 529
  • DetailCraft 0.8.1
/**
 * A cantilever wall with a level backfill. wall = { H (retained height of soil above the base's top, mm), hb (base thickness), t (stem thickness,
 * constant), toe, heel (mm), embed (soil over the toe, mm, ignored for passive) }. soil = { gamma (kN/m³), phi (degrees), surcharge (kPa), qa,
 * mu (base friction), fsSlide, fsOt, gammaConc }. Active pressure is Rankine, Ka = tan²(45° − φ/2), on the vertical plane through the heel,
 * with the soil on the heel and the stem as weight; passive pressure is left out.
 */
export function retainingWall(wall, soil = {}) {
  const s = { gamma: 18, phi: 30, surcharge: 0, qa: 0, mu: 0, fsSlide: 1.5, fsOt: 1.5, gammaConc: 24, ...soil };
  const H = wall.H / 1000, hb = wall.hb / 1000, t = wall.t / 1000, toe = wall.toe / 1000, heel = wall.heel / 1000;
  const Bw = toe + t + heel, Ht = H + hb;
  const Ka = Math.tan((45 - s.phi / 2) * Math.PI / 180) ** 2;
  const Pa = 0.5 * Ka * s.gamma * Ht * Ht, Ps = Ka * s.surcharge * Ht;
  const MoPa = Pa * Ht / 3, MoPs = Ps * Ht / 2;
  // weights per metre of wall, arms about the toe's front edge
  const parts = [
    { name: 'base', W: s.gammaConc * Bw * hb, x: Bw / 2 },
    { name: 'stem', W: s.gammaConc * t * H, x: toe + t / 2 },
    { name: 'soil', W: s.gamma * heel * H, x: toe + t + heel / 2 },
    { name: 'surcharge', W: s.surcharge * heel, x: toe + t + heel / 2 },
  ];
  const W = parts.reduce((a, p) => a + p.W, 0), Mr = parts.reduce((a, p) => a + p.W * p.x, 0);
  const Ho = Pa + Ps, Mo = MoPa + MoPs;
  const e = Bw / 2 - (Mr - Mo) / W;   // eccentricity of the resultant from the middle of the base
  const field = pressureField(W, e * 1000, 0, Bw * 1000, 1000);
  const checks = [];
  checks.push(mk('wall-overturning', 'Rankine; overturning about the toe', s.fsOt * Mo, Mr, 'kN·m/m', { fs: r2(Mr / Mo), need: s.fsOt, Mo: r1(Mo), Mr: r1(Mr) }));
  if (s.mu > 0) checks.push(mk('wall-sliding', 'Rankine; sliding on the base', s.fsSlide * Ho, s.mu * W, 'kN/m', { fs: r2(s.mu * W / Ho), need: s.fsSlide, H: r1(Ho), W: r1(W) }));
  else checks.push(note('wall-sliding', 'Rankine; sliding on the base', { H: r1(Ho) }));
  if (!field.ok) checks.push({ id: 'wall-bearing', clause: 'base pressure', status: 'fail', unit: 'kPa', demand: Infinity, capacity: s.qa, ratio: Infinity, vars: { reason: field.reason, e: r2(e) } });
  else if (s.qa > 0) checks.push(mk('wall-bearing', 'base pressure', field.qmax, s.qa, 'kPa', { q: r1(field.qmax), qa: s.qa, e: r2(e), kern: r2(Bw / 6) }));
  else checks.push(note('wall-bearing', 'base pressure', { q: r1(field.qmax) }));
  return { Ka, Pa, Ps, W, Mr, Mo, e, Bw, field, parts, checks };
}

In detail

Method
Corner settlement of a flexible rectangle on an elastic half-space (the file cites Timoshenko and Goodier); Winkler springs under plate elements; elastic method for pile groups; Rankine pressure for walls.
Number types
64-bit numbers.
Tolerances
Pile and column moment shares need Σx² or Σy² above 1e-12 m². Pile equilibrium flag 1e-6 of the load. Node lookups in the spring pressure 1e-6 mm.
Limits
The mat's peak moments and settlements change with the mesh. The spring pressure between nodes is read from each element's bounding rectangle; a corner of that rectangle with no node counts as zero. Concrete stiffness is 4700·√f′c MPa with Poisson's ratio 0.2.

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