Resources Numerical accuracy Calculation policy

Voxel stress analysis: 32-bit steps in loads and results

The safety factor, the values under the result probe, and the direction of face loads in a voxel stress study.

How the precision is kept

Three places in the study pass a number through 32-bit. The minimum safety factor divides the yield stress by a maximum that was read back from a 32-bit array. The per-vertex stress and displacement that the probe reads are stored in Float32Array. Force and pressure loads on faces take their direction from a Float32Array of node normals built in voxel.js.

Open item. Divide by the 64-bit maximum for the safety factor, keep the sampled values in 64-bit for the probe, and build the node normals in a Float64Array.

Calculations under this policy

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

fea.js PartCraft 0.8.1 32-bit in part 3 calculations Reviewed

Tests tools/partcraft-test/test-sim08.mjs, tools/partcraft-test/test-craftlib.mjs

Minimum safety factor

32-bit in part

The yield stress divided by the largest nodal von Mises stress.

safety = S_y ÷ σ_max

σ_max here is vMax, which was taken from the 32-bit vmNode array a few lines above. The safety factor therefore carries about 7 digits. The maximum element stress in the same block is 64-bit.

  • fea.js
  • lines 434 to 437
  • PartCraft 0.8.1
maxVm: { value: vMax, node: vAt, at: vAt >= 0 ? VX.nodeXYZ(g, vAt) : null },
maxVmElem: { value: elMax, at: elMaxAt >= 0 ? VX.elemCentre(g, model.elems[elMaxAt]) : null },
maxDisp: { value: dMax, node: dAt, at: dAt >= 0 ? VX.nodeXYZ(g, dAt) : null, vec: dAt >= 0 ? [u[3 * dAt], u[3 * dAt + 1], u[3 * dAt + 2]] : null },
minSafety: vMax > 0 ? yieldStress / vMax : Infinity,

Values on the plot mesh

32-bit in part

Stress, displacement size and displacement vector at each vertex of the plot mesh, read by the hover probe and the legend.

Each value is sampled in 64-bit and then stored in a Float32Array. The nodal stress it samples was already read from the 32-bit vmNode array, and the plot mesh's vertex positions are 32-bit as well.

  • fea.js
  • lines 465 to 476
  • PartCraft 0.8.1
/** Per-vertex values of the part mesh: { vm, disp (magnitude), u (xyz) } as Float32Arrays. */
export function onMesh(model, res, u, mesh) {
  const nv = mesh.positions.length / 3, vm = new Float32Array(nv), disp = new Float32Array(nv), uv = new Float32Array(nv * 3);
  const one = new Float64Array(1), three = new Float64Array(3), vmv = Float64Array.from(res.vmNode, (v) => (Number.isFinite(v) ? v : 0));
  for (let v = 0; v < nv; v++) {
    const p = [mesh.positions[v * 3], mesh.positions[v * 3 + 1], mesh.positions[v * 3 + 2]];
    sampleField(model, vmv, 1, p, one); vm[v] = one[0];
    sampleField(model, u, 3, p, three); uv[v * 3] = three[0]; uv[v * 3 + 1] = three[1]; uv[v * 3 + 2] = three[2];
    disp[v] = Math.hypot(three[0], three[1], three[2]);
  }
  return { vm, disp, u: uv };
}

Force and pressure on faces

32-bit in part

Shares a force or a pressure over the nodes of the picked faces.

node force = magnitude × weightᵢ ÷ Σ weight × direction; pressure magnitude = p × area

The share and the area are 64-bit, and the pressure area is the kernel's exact face area when it is given. Unless the force has its own direction, the direction is read from sel.normal, a Float32Array of unit node normals made by faceNodes in voxel.js, so each load component carries 32-bit rounding of that normal.

  • fea.js
  • lines 368 to 381
  • PartCraft 0.8.1
const sel = fn(l);
let wsum = 0; sel.nodes.forEach((n, i) => { if (okNode(n)) wsum += sel.weight[i]; });
if (!(wsum > 0)) throw new Error(t('study.fea.load_no_nodes', { type: typeWord(l.type) }));
let mag, vec = null;
if (l.type === 'force') { mag = l.total; if (Array.isArray(l.dir)) { const dl = Math.hypot(...l.dir) || 1; vec = l.dir.map((v) => v / dl); } }
else { const A = exactArea && l.faces ? l.faces.reduce((s, fi) => s + (exactArea[fi] || 0), 0) : sel.quadArea || wsum; mag = l.p * A; }
sel.nodes.forEach((n, i) => {
  if (!okNode(n)) return;
  nodes.push(n);
  const share = mag * sel.weight[i] / wsum;
  const d = vec || [-sel.normal[i * 3], -sel.normal[i * 3 + 1], -sel.normal[i * 3 + 2]];
  for (let a = 0; a < 3; a++) { f[3 * n + a] += share * d[a]; total[a] += share * d[a]; }
});
groups.loads.push({ type: l.type, nodes: Int32Array.from(nodes) });

In detail

Method
Safety factor = yield ÷ largest nodal von Mises stress. Probe values by trilinear sampling at the plot mesh's vertices. Face loads shared by tributary weight along the node normal.
Number types
Float64Array for the solution; Float32Array for nodal stress, per-vertex plot values and node normals.
Limits
These values carry about 7 significant digits. The voxel mesh is a larger limit on the result than the 32-bit steps.

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