Resources Numerical accuracy Calculation policy

Result fields sent to the plot, legend and probe

Copies the solved temperature, heat flux, stress, strain and displacement values onto the plot mesh that the result view colors, labels and probes.

How the precision is kept

The worker copies each nodal result into a Float32Array with one value per plot vertex. The probe readout in ws-simulation.js and the temperature legend range in sim-thermal-ui.js read these 32-bit arrays, so a probed value and the legend limits carry 32-bit precision. The summary values (maxima, reactions, heat balance, frequencies, load factors) are passed through unchanged from the solver.

Open item. Send the per-vertex result arrays as Float64Array, or have the probe and the legend read the solver's 64-bit values, and keep 32-bit copies only for coloring.

Calculations under this policy

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

tet-worker.js PartCraft 0.8.1 32-bit in part 4 calculations Reviewed

Tests tools/partcraft-test/test-tet.mjs

Nodal values onto plot vertices

32-bit in part

Copies a solved nodal field (stress, displacement, temperature, strain) to the vertices of the plot mesh.

The output is a Float32Array, so each 64-bit nodal value is rounded to 32-bit on the copy. The probe in ws-simulation.js interpolates its readout from these arrays.

  • tet-worker.js
  • line 35
  • PartCraft 0.8.1
const at = (pm, values, comps = 1) => { const out = new Float32Array(pm.node.length * comps); pm.node.forEach((n, v) => { for (let c = 0; c < comps; c++) out[comps * v + c] = values[comps * n + c]; }); return out; };

Heat flux size per vertex

32-bit in part

The heat flux magnitude in W/m² at each plot vertex.

q = flux × 10⁶; |q| = √(q_x² + q_y² + q_z²)

The unit change is done in a Float64Array, but the three components are then copied to 32-bit and the magnitude is computed from those 32-bit values and stored in a Float32Array. A flux that is not finite is replaced by 0.

  • tet-worker.js
  • lines 51 to 52
  • PartCraft 0.8.1
const T = at(pm, r.T), qn = Float64Array.from(r.flux.node, (v) => (Number.isFinite(v) ? v * 1e6 : 0)), qv = at(pm, qn, 3), q = new Float32Array(T.length);
for (let v = 0; v < q.length; v++) q[v] = Math.hypot(qv[3 * v], qv[3 * v + 1], qv[3 * v + 2]);

Mode shape per vertex

32-bit in part

A vibration or buckling mode shape scaled so its largest displacement is 1.

shape = u ÷ max |u|

The shape is copied to 32-bit first; the largest magnitude is then found and divided out in 32-bit storage. A zero shape is divided by 1.

  • tet-worker.js
  • line 36
  • PartCraft 0.8.1
const shapeAt = (pm, u) => { const out = at(pm, u, 3); let mx = 0; for (let v = 0; v < out.length; v += 3) mx = Math.max(mx, Math.hypot(out[v], out[v + 1], out[v + 2])); for (let i = 0; i < out.length; i++) out[i] /= mx || 1; return out; };

Plot mesh of the tetrahedral model

32-bit in part

The surface of the tetrahedral mesh as triangles for the result view, each boundary face split in four through its midside nodes.

Vertex positions are copied from the model's Float64Array into a Float32Array. The probe in ws-simulation.js reports its location from these positions.

  • tet-worker.js
  • lines 20 to 34
  • PartCraft 0.8.1
/** The boundary of a T10 model as a plot mesh → { positions, indices, triFace, lines, node (model node per vertex) } */
export function tetPlotMesh(model) {
  const map = new Map(), node = [];
  const vid = (n) => { let v = map.get(n); if (v === undefined) { v = node.length; map.set(n, v); node.push(n); } return v; };
  const nb = model.btri6.length / 6, idx = new Uint32Array(nb * 12), tf = new Int32Array(nb * 4), seen = new Set(), lines = [];
  for (let t = 0; t < nb; t++) {
    const [c0, c1, c2, m01, m12, m20] = Array.from(model.btri6.subarray(6 * t, 6 * t + 6), vid);
    idx.set([c0, m01, m20, m01, c1, m12, m20, m12, c2, m01, m12, m20], 12 * t);
    tf.fill(model.bface[t], 4 * t, 4 * t + 4);
    for (const [a, m, b] of [[c0, m01, c1], [c1, m12, c2], [c2, m20, c0]]) { const k = Math.min(a, b) * 4294967296 + Math.max(a, b); if (seen.has(k)) continue; seen.add(k); lines.push(a, m, m, b); }
  }
  const positions = new Float32Array(3 * node.length);
  node.forEach((n, v) => { positions[3 * v] = model.X[3 * n]; positions[3 * v + 1] = model.X[3 * n + 1]; positions[3 * v + 2] = model.X[3 * n + 2]; });
  return { positions, indices: idx, triFace: tf, lines: Uint32Array.from(lines), node: Int32Array.from(node) };
}
thermal-worker.js PartCraft 0.8.1 32-bit in part 4 calculations Reviewed

Tests tools/partcraft-test/test-thermal.mjs

Grid field sampled at plot vertices

32-bit in part

Samples a voxel-grid result (temperature, flux, temperature rise, mode shape) at each vertex of the plot mesh.

sampleField in fea.js fills a Float64Array per vertex, and the value is then stored in a Float32Array. The plot mesh positions it samples at are read as handed in by refineMesh.

  • thermal-worker.js
  • lines 28 to 33
  • PartCraft 0.8.1
/** A nodal grid field (comps per node) at every plot-mesh vertex. */
function sample(model, values, comps, pm) {
  const nv = pm.positions.length / 3, out = new Float32Array(nv * comps), t = new Float64Array(comps);
  for (let v = 0; v < nv; v++) { sampleField(model, values, comps, [pm.positions[3 * v], pm.positions[3 * v + 1], pm.positions[3 * v + 2]], t); for (let c = 0; c < comps; c++) out[comps * v + c] = t[c]; }
  return out;
}

Heat flux size per vertex on a voxel mesh

32-bit in part

The heat flux magnitude in W/m² at each plot vertex of a voxel study.

q = flux × 10⁶; |q| = √(q_x² + q_y² + q_z²)

The W/mm² to W/m² change is done in 64-bit; the sampled components are stored in 32-bit and the magnitude is computed from them into a Float32Array.

  • thermal-worker.js
  • lines 38 to 41
  • PartCraft 0.8.1
const T = sample(r.model, r.T, 1, pm);
const qn = Float64Array.from(r.flux.node, (v) => (Number.isFinite(v) ? v * 1e6 : 0));   // W/mm² → W/m²
const qv = sample(r.model, qn, 3, pm), q = new Float32Array(T.length);
for (let v = 0; v < q.length; v++) q[v] = Math.hypot(qv[3 * v], qv[3 * v + 1], qv[3 * v + 2]);

Buckling mode shape on a voxel mesh

32-bit in part

A buckling mode shape sampled on the plot mesh and scaled to a largest displacement of 1.

shape = u ÷ max |u|

Sampled into a Float32Array, then divided by the largest 32-bit magnitude (by 1 when that is zero).

  • thermal-worker.js
  • lines 68 to 70
  • PartCraft 0.8.1
const out = sample(r.model, u, 3, pm);
let mx = 0; for (let v = 0; v < out.length; v += 3) mx = Math.max(mx, Math.hypot(out[v], out[v + 1], out[v + 2]));
for (let i = 0; i < out.length; i++) out[i] /= mx || 1;

Fields of each time step on tetrahedra

32-bit in part

Copies the stress, displacement and temperature-rise fields of every stored time step to the plot vertices.

Each field is copied into a new Float32Array per step. The history's peak values come from the solver result, not from these arrays.

  • thermal-worker.js
  • line 84
  • PartCraft 0.8.1
const at = (values, comps = 1) => { const out = new Float32Array(pm.node.length * comps); pm.node.forEach((n, v) => { for (let c = 0; c < comps; c++) out[comps * v + c] = values[comps * n + c]; }); return out; };

In detail

Method
On tetrahedral meshes the nodal value is copied to its plot vertex with no resampling. On voxel meshes the grid field is sampled at each plot vertex by sampleField in fea.js. Heat flux is converted from W/mm² to W/m² by × 1e6 in 64-bit before the copy; its magnitude is then computed from the 32-bit components.
Number types
Float32Array for every per-vertex field and for the plot mesh positions; Float64Array for the flux before the copy; Uint32Array and Int32Array for indexes.
Limits
Mode shapes are divided by their largest 32-bit magnitude and are relative shapes only.

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