Resources Numerical accuracy Calculation policy

Slope stability

Finds the factor of safety of a circular slip surface by Bishop's simplified method or the ordinary method of slices, and searches for the critical circle.

How the precision is kept

64-bit arithmetic. The factor of safety depends on the number of slices (10 to 400), on an iteration that stops at an absolute change of 1e-5, and on a search over a grid of centers and radii that returns the lowest value tried. The unit weight of water is 9.81 kN/m³.

Open item. Report when the Bishop iteration ends at its 200-pass limit without meeting the tolerance.

Calculations under this policy

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

geotech.js CivilCraft 0.8.1 64-bit 6 calculations Reviewed

Tests bin/test-civilcraft-geotech.mjs

Unit weight of water

64-bit

The constant behind every pore pressure and every submerged unit weight in this file.

γw = 9.81 kN/m³

A constant rounded to three digits. With standard gravity 9.80665 m/s² and 1000 kg/m³ the value is 9.80665 kN/m³, so pore pressures here are about 0.03 % higher than that.

  • geotech.js
  • line 31
  • CivilCraft 0.8.1
const G_W = 9.81;   // kN/m³, unit weight of water

Elevation of a line at a position

64-bit

The ground, a layer top or the water table at a horizontal position.

Linear interpolation between the two neighboring points, found by bisection on the index. Beyond either end the end value is held level. A vertical step returns the second point's elevation.

  • geotech.js
  • lines 222 to 231
  • CivilCraft 0.8.1
const interp = (poly, x) => {
  if (typeof poly === 'number') return poly;
  const n = poly.length;
  if (x <= poly[0].x) return poly[0].z;
  if (x >= poly[n - 1].x) return poly[n - 1].z;
  let lo = 0, hi = n - 1;
  while (hi - lo > 1) { const m = (lo + hi) >> 1; if (poly[m].x <= x) lo = m; else hi = m; }
  const a = poly[lo], b = poly[hi];
  return b.x === a.x ? b.z : a.z + ((x - a.x) / (b.x - a.x)) * (b.z - a.z);
};

Where a circle meets the ground

64-bit

The positions where the lower arc of a trial circle crosses the ground line.

f(x) = ground(x) − (zc − √(R² − (x − xc)²))

The span is scanned in 400 equal steps for a change of sign, and each change is refined by 40 bisections. Two crossings inside one scan step cancel and are not found. The square root's argument is clamped at zero.

  • geotech.js
  • lines 242 to 253
  • CivilCraft 0.8.1
function circleGround(geom, c) {
  // the lower arc meets the ground where ground(x) = zc − √(R² − (x − xc)²); scan, then refine by bisection
  const gx = geom.ground, xa = Math.max(gx[0].x, c.xc - c.R), xb = Math.min(gx[gx.length - 1].x, c.xc + c.R), n = 400;
  const f = (x) => interp(gx, x) - (c.zc - Math.sqrt(Math.max(0, c.R * c.R - (x - c.xc) ** 2)));   // > 0 where the ground is above the arc (inside)
  const roots = []; let px = xa, pf = f(xa);
  for (let i = 1; i <= n; i++) {
    const x = xa + ((xb - xa) * i) / n, v = f(x);
    if ((pf > 0) !== (v > 0)) { let lo = px, hi = x; for (let k = 0; k < 40; k++) { const m = (lo + hi) / 2; if ((f(lo) > 0) === (f(m) > 0)) lo = m; else hi = m; } roots.push({ x: (lo + hi) / 2, into: v > 0 }); }
    px = x; pf = v;
  }
  return { roots, inside0: f(xa) > 0, inside1: f(xb) > 0, xa, xb };
}

Slices of the sliding mass

64-bit

Each slice's width, weight, base angle, strength and pore pressure.

W = Σ layers γ × thickness × b; sin α = −(x − xc) ÷ R; u = γw × (zw − zb), or ru × W ÷ b

Slices are of equal arc angle, so they narrow where the arc is steep. Weight uses the layer thicknesses at the slice's middle times its width, a rectangle rule. cos α is √(1 − sin²α), clamped at zero. The slice count is made whole with a bitwise OR; it is a count, not a measured value.

  • geotech.js
  • lines 267 to 294
  • CivilCraft 0.8.1
const n = Math.max(10, Math.min(400, slices | 0)), S = [];
const th0 = Math.asin(clamp((xl - c.xc) / c.R, -1, 1)), th1 = Math.asin(clamp((xr - c.xc) / c.R, -1, 1)), xAt = (i) => c.xc + c.R * Math.sin(th0 + ((th1 - th0) * i) / n);
const layerAt = (x, z) => {
  for (let i = geom.layers.length - 1; i >= 0; i--) if (z <= interp(geom.layers[i].top, x) + 1e-12 || i === 0) return geom.layers[i];
  return geom.layers[0];
};
const tops = (x) => geom.layers.map((l) => Math.min(interp(l.top, x), interp(geom.ground, x)));
for (let i = 0; i < n; i++) {
  const xa = xAt(i), b = xAt(i + 1) - xa, x = xa + b / 2, zg = interp(geom.ground, x), dxc = x - c.xc, h = c.R * c.R - dxc * dxc;
  if (h < 0) continue;
  const zb = c.zc - Math.sqrt(h);
  if (zb >= zg - 1e-9) continue;
  if (zb < geom.floor - 1e-9) problems.push({ code: 'geo.slope.floor' });
  // weight: the thickness of each layer between the base and the ground
  const t = tops(x); let W = 0, zsum = 0;
  for (let k = 0; k < geom.layers.length; k++) {
    const hi = Math.min(t[k], zg), lo = k + 1 < geom.layers.length ? Math.min(t[k + 1], zg) : -Infinity, a = Math.max(lo, zb), top = hi;
    if (top > a) { const w = geom.layers[k].gamma * (top - a) * b; W += w; zsum += w * (top + a) / 2; }
  }
  let q = 0; for (const s of geom.surcharge || []) if (x >= s.x0 && x <= s.x1) q += s.q * b;
  const Wt = W + q, zcg = Wt > 0 ? (zsum + q * zg) / Wt : zb;
  const lay = layerAt(x, zb + 1e-9);
  const cc = lay.cu !== undefined ? lay.cu : lay.c, ph = lay.cu !== undefined ? 0 : lay.phi;
  const sinA = -dxc / c.R, cosA = Math.sqrt(Math.max(0, 1 - sinA * sinA));
  let u = 0;
  if (geom.water !== undefined && geom.water !== null) { const zw = interp(geom.water, x); if (zw > zb) u = G_W * (zw - zb); }
  else if (geom.ru) u = geom.ru * (W / b);
  S.push({ x, b, zb, zg, W: Wt, sinA, cosA, c: cc, phi: ph, u, zcg });

Factor of safety

64-bit

The factor of safety of one circle by the ordinary method or by Bishop's simplified method.

Bishop: FS = Σ[(c·b + (W − u·b)·tan φ) ÷ (cos α + sin α·tan φ ÷ FS)] ÷ Σ W·sin α

Bishop's equation is iterated from 1.5 until the change is below 1e-5, an absolute tolerance, or for 200 passes; there is no warning if the limit is reached. The effective weight term is clamped at zero and mα is floored at 1e-6. The ordinary method divides by cos α with no guard.

  • geotech.js
  • lines 297 to 318
  • CivilCraft 0.8.1
let drive = 0; for (const s of S) { drive += s.W * s.sinA; if (kh) drive += (kh * s.W * (c.zc - s.zcg)) / c.R; }
if (drive <= 1e-9) return { fs: Infinity, ok: true, slices: S, moment: drive, problems: [{ code: 'geo.slope.nodrive' }], xl, xr };
let fs;
if (method === 'ordinary') {
  let res = 0; for (const s of S) res += (s.c * s.b) / s.cosA + Math.max(0, s.W * s.cosA - (s.u * s.b) / s.cosA) * Math.tan(s.phi * RAD);
  fs = res / drive;
} else {
  fs = 1.5;
  for (let it = 0; it < 200; it++) {
    let res = 0, bad = false;
    for (const s of S) {
      const m = s.cosA + (s.sinA * Math.tan(s.phi * RAD)) / fs;
      if (m < 0.2) bad = true;
      res += (s.c * s.b + Math.max(0, s.W - s.u * s.b) * Math.tan(s.phi * RAD)) / Math.max(m, 1e-6);
    }
    const next = res / drive;
    if (bad && !problems.some((p) => p.code === 'geo.slope.ma')) problems.push({ code: 'geo.slope.ma' });
    if (Math.abs(next - fs) < tol) { fs = next; break; }
    fs = next;
  }
}
return { fs, ok: fin(fs), slices: S, moment: drive, problems, xl, xr, mirrored: !!geom.mirrored };

In detail

Method
Method of slices on a circle. Bishop simplified: FS = Σ[(c·b + (W − u·b)·tan φ) ÷ mα] ÷ Σ W·sin α with mα = cos α + sin α·tan φ ÷ FS, by fixed-point iteration; ordinary method without iteration. The header cites Fellenius 1936, Bishop 1955 and Duncan, Wright and Brandon 2014, ch. 6.
Number types
64-bit numbers. Water unit weight 9.81 kN/m³.
Tolerances
Bishop iteration: |change in FS| below 1e-5 (absolute), at most 200 passes, starting from 1.5. mα below 0.2 is reported; it is floored at 1e-6. Circle and ground crossings: 400 scan steps, then 40 bisections. Slice kept when its base is more than 1e-9 below the ground. Driving moment of 1e-9 or less gives FS = Infinity. Slip surface shorter than 1e-6 refused.
Limits
Circular surfaces only. A slice's weight is its mid-width thickness times its width. The search is a sampled one and does not prove a global minimum. If the Bishop iteration uses all 200 passes, the last value is returned without a warning.

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