Resources Numerical accuracy Calculation policy

Rebar congestion map of a footing

Shows how tight the openings between reinforcing bars are for concrete to pass, and reports the tightest place.

How the precision is kept

Openings and ratios are computed in 64-bit at the center of each grid cell, and the worst place and the count of tight cells are taken from those 64-bit values. The map itself is stored in two Float32Array. The share of tight cells that is shown to the person is counted again from the 32-bit copy.

Open item. Count the tight share from the 64-bit ratios, as the over count in congestion already does, instead of reading the 32-bit map back.

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.

congestion.js DetailCraft 0.8.1 32-bit in part 6 calculations Reviewed

Tests tools/detailcraft-test/test-congestion.mjs

Clear spacing a bar asks for

64-bit

The least clear distance between parallel bars for a bar size and aggregate.

max(25.4, d_b, 4/3 × aggregate) mm; BS bars: max(d_b, aggregate + 5, 20) mm

25.4 mm is one inch exactly. The 4/3 rule is evaluated as aggregate × 4 ÷ 3 in 64-bit. The result is a maximum of positive values, so nothing cancels.

  • congestion.js
  • lines 21 to 25
  • DetailCraft 0.8.1
/** The clear spacing a bar of this size asks for between parallel bars (mm). */
export function clearNeed(bar, { aggregate = 0 } = {}) {
  if (bar.set === 'bs') return Math.max(bar.db, aggregate ? aggregate + 5 : 0, 20);
  return Math.max(25.4, bar.db, aggregate ? aggregate * 4 / 3 : 0);
}

Free gap at a point

64-bit

The length of the free stretch that contains a point, between the bars either side of it.

gap = right − left; 0 inside a bar

Comparisons and one subtraction in 64-bit. A point exactly on a bar's edge counts as inside the bar, because the test uses ≤ and ≥.

  • congestion.js
  • lines 27 to 36
  • DetailCraft 0.8.1
/** The free interval containing `at` among blocking intervals [a, b] on a line from lo to hi. Returns its length (0 when `at` is inside a bar). */
function gapAt(at, intervals, lo, hi) {
  let left = lo, right = hi;
  for (const [a, b] of intervals) {
    if (at >= a && at <= b) return 0;
    if (b <= at && b > left) left = b;
    if (a >= at && a < right) right = a;
  }
  return Math.max(0, right - left);
}

Need and grid size

64-bit

Sets what the concrete needs and how fine the map is.

cell = max(5, given, or max(10, ⌈max(L, B) ÷ 160 ÷ 5⌉ × 5)); columns = round(L ÷ cell)

The cell is at least 5 mm and by default about 1/160 of the longer side, rounded up to a multiple of 5 mm. Column and row counts are rounded, so the cell actually used is L ÷ columns by B ÷ rows.

  • congestion.js
  • lines 62 to 65
  • DetailCraft 0.8.1
const need = Math.max(Number(opts.vibrator) || 0, clearNeed(bx, opts), clearNeed(by, opts), starter ? clearNeed(starter, opts) : 0);
const cell = Math.max(5, Number(opts.cell) || Math.max(10, Math.ceil(Math.max(L, B) / 160 / 5) * 5));
const cols = Math.max(1, Math.round(L / cell)), rows = Math.max(1, Math.round(B / cell));
const x0 = -L / 2, y0 = -B / 2, cw = L / cols, ch = B / rows;

Opening and ratio per cell, and the worst place

64-bit

Fills the map and finds the tightest opening and the number of cells that are too tight.

opening = min of the gaps in each layer; ratio = need ÷ opening

Gap and ratio are 64-bit locals, and worst and over are taken from them before anything is read back. The two maps are Float32Array copies, which the drawing and tightShare use. A cell inside a bar gets need ÷ 0.5 as its ratio and is not counted as tight.

  • congestion.js
  • lines 71 to 88
  • DetailCraft 0.8.1
const values = new Float32Array(cols * rows), opening = new Float32Array(cols * rows);
let worst = { x: 0, y: 0, ratio: 0, opening: Infinity }, over = 0;
const colLists = [];
for (let i = 0; i < cols; i++) { const x = x0 + (i + 0.5) * cw; colLists.push([throughY(LAYER_Y, x), throughY(LAYER_H, x)]); }
for (let j = 0; j < rows; j++) {
  const y = y0 + (j + 0.5) * ch, rowX = through(LAYER_X, y), rowH = through(LAYER_H, y);
  for (let i = 0; i < cols; i++) {
    const x = x0 + (i + 0.5) * cw;
    const gap = Math.min(gapAt(x, rowX, -L / 2 + c, L / 2 - c), gapAt(y, colLists[i][0], -B / 2 + c, B / 2 - c),
      gapAt(x, rowH, -L / 2 + c, L / 2 - c), gapAt(y, colLists[i][1], -B / 2 + c, B / 2 - c));
    opening[j * cols + i] = gap;
    const ratio = gap > 0 ? need / gap : need / 0.5;   // inside a bar: its own number, large
    values[j * cols + i] = ratio;
    if (gap > 0 && ratio > 1) over++;   // a cell inside a bar is a bar, not a tight opening
    if (gap > 0 && ratio > worst.ratio) worst = { x, y, ratio, opening: gap };
  }
}
return { cols, rows, cell: cw, x0, y0, values, opening, need, worst, over, share: over / Math.max(1, values.reduce((n, _, k) => n + (opening[k] > 0 ? 1 : 0), 0)), bars, aggregate: Number(opts.aggregate) || 0, vibrator: Number(opts.vibrator) || 0 };

Share of the open area that is too tight

32-bit in part

The fraction of free cells whose ratio is over 1.

share = cells with ratio > 1 ÷ cells with opening > 0

Both tests read the Float32Array maps. A ratio between 1 and about 1.00000006 is stored as exactly 1 in 32 bits and is not counted here, although congestion counted it in over. This share decides whether a problem is reported and is the percentage shown.

  • congestion.js
  • lines 91 to 99
  • DetailCraft 0.8.1
/**
 * The cells over 1 that are NOT a bar itself: concrete has to get down there. Cells inside a bar are ratio need/0.5 and skipped
 * (a bar is not an opening). Returns the share of the free cells that are too tight.
 */
export function tightShare(map) {
  let free = 0, tight = 0;
  for (let k = 0; k < map.values.length; k++) { if (map.opening[k] <= 0) continue; free++; if (map.values[k] > 1) tight++; }
  return free ? tight / free : 0;
}

The congestion problem

32-bit in part

The warning or error listed for a footing whose steel is too tight.

Nothing is reported unless the worst 64-bit ratio exceeds 1 and the 32-bit share is above 0. Opening and need are rounded to 0.1 mm, the share to a whole percent and the position to a whole millimeter, in the message and in the stored required and found values. It is an error when the opening is under half the need, otherwise a warning.

  • congestion.js
  • lines 113 to 120
  • DetailCraft 0.8.1
export function congestionProblems(map, { label = '', id = '', t = null, footing = null } = {}) {
  const share = tightShare(map);
  if (map.worst.ratio <= 1 || share === 0) return [];
  const vars = { mark: label, opening: Math.round(map.worst.opening * 10) / 10, need: Math.round(map.need * 10) / 10, pct: Math.round(share * 100), x: Math.round(map.worst.x), y: Math.round(map.worst.y) };
  const text = t ? t('cong.prob.tight', vars) : `${label}: the tightest place leaves ${vars.opening} mm for concrete to get down; ${vars.need} mm is needed (${vars.pct}% of the open area is tighter).`;
  return [{ id: 'cong:tight:' + id, code: 'cong.tight', severity: map.worst.opening < map.need / 2 ? 'error' : 'warning', msg: { key: 'cong.prob.tight', vars }, text, about: [{ kind: 'footing', id, label }],
    source: { kind: 'engine', name: 'detailcraft.congestion' }, req: { op: '>=', value: Math.round(map.need * 10) / 10, unit: 'mm' }, found: { value: vars.opening, unit: 'mm' } }];
}

In detail

Method
Need = the largest of the vibrator diameter and each bar's clear spacing rule: max(25.4 mm, d_b, 4/3 × aggregate), or for BS bars max(d_b, aggregate + 5 mm, 20 mm); the header cites ACI 318-19 25.2.1 and EN 1992-1-1 8.2. Opening = the narrowest free gap in any one layer of steel at the cell center. Value = need ÷ opening.
Number types
64-bit numbers for gaps and ratios; Float32Array for the two stored maps.
Tolerances
A starter bar within 1e-9 of the center in both directions has its hook placed along x.
Limits
Sampled at cell centers: a gap narrower than a cell can fall between samples. The reported opening and need are rounded to 0.1 mm and the share to a whole percent.

Approach: 32-bit step, flagged

One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.

Every policy that takes this approach