/ concept-collection / turing-surface
Sign in
concept-collection / turing-surface
124 lines · 5.1 KBCodeBlameHistory
2 * Reading a reference HDF5 file into the pieces a replay needs.
3 *
4 * A reference file is a saved run from an independently-implemented solver —
5 * geometry, initial and final spherical-harmonic coefficients, and the run's
6 * parameters — in the layout documented in docs/ellipsoid-reference-spec.md.
7 * Two things read it: the `npm run ref` CLI (through `h5wasm/node`) and the
8 * browser's compare mode (through `h5wasm`, lazily loaded — see
9 * referenceFile.ts). Both hand this module the same object shape, so the
10 * format knowledge lives once.
11 */
12import { mModelByKey, defaultParams, type MModel, type Params } from '../mgpu/registry.ts';
13import { mGeometryByKey, defaultGeometryParams, type MGeometry } from '../geom/registry.ts';
14import { nlmCalc } from '../sht/layout.ts';
16/** The slice of h5wasm's File/Group/Dataset API this reader touches — enough
17 * that the node and browser builds both satisfy it structurally. */
18export interface H5Node {
19 attrs: Record<string, { value: unknown }>;
20 get(name: string): unknown;
23export interface ReferenceCase {
24 /** Where it came from — the file name, for labels and messages. */
25 label: string;
26 model: MModel;
27 geometry: MGeometry;
28 /** The model's defaults overlaid with the file's own — `dt` included, so
29 * `steps * params.dt` is the file's end time. */
30 params: Params;
31 geometryParams: Params;
32 lmax: number;
33 /** The solve-iteration count recorded in the file — the replay's default. */
34 niter: number;
35 /** Steps at `params.dt` from the initial state to the final one. */
36 steps: number;
37 /** The band-limited surface's own coefficients, [re, im] per (l, m). The
38 * reference solver ran on this exact surface, not the analytic shape. */
39 geometryCoeffs: { X: Float32Array; Y: Float32Array; Z: Float32Array };
40 /** Spectral state per species (keyed by `model.state` name) at t = 0. */
41 initial: Record<string, Float32Array>;
42 /** The same, at the end time. */
43 final: Record<string, Float32Array>;
46const attrsOf = (node: H5Node): Record<string, unknown> =>
47 Object.fromEntries(Object.entries(node.attrs).map(([k, v]) => [k, v.value]));
49/** Attributes as numbers — h5wasm hands back number or BigInt by dtype. */
50const numberAttrs = (node: H5Node): Params =>
51 Object.fromEntries(Object.entries(attrsOf(node)).map(([k, v]) => [k, Number(v)]));
53function groupOf(node: H5Node, name: string): H5Node {
54 const g = node.get(name) as H5Node | null;
55 if (!g || typeof g.get !== 'function') {
56 throw new Error(`no '${name}/' group — is this a reference file?`);
57 }
58 return g;
61function coeffsOf(group: H5Node, groupName: string, name: string, nlm: number): Float32Array {
62 const v = (group.get(name) as { value?: unknown } | null)?.value;
63 if (!(v instanceof Float32Array)) {
64 throw new Error(`'${groupName}/${name}' is not a float32 dataset`);
65 }
66 if (v.length !== 2 * nlm) {
67 throw new Error(`'${groupName}/${name}' has ${v.length} values, expected 2*nlm = ${2 * nlm}`);
68 }
69 return v;
72/** Read an open reference file. Throws with a plain message on anything the
73 * replay could not act on — unknown model or geometry, missing or misshapen
74 * coefficients — so both the CLI and the page can just show it. */
75export function extractReferenceCase(file: H5Node, label: string): ReferenceCase {
76 const modelKey = String(attrsOf(file).model);
77 const model = mModelByKey(modelKey);
78 if (!model) throw new Error(`unknown model '${modelKey}'`);
80 const spec = groupOf(file, 'spec');
81 const specAttrs = attrsOf(spec);
82 const geometryKey = String(specAttrs.geometry);
83 const geometry = mGeometryByKey(geometryKey);
84 if (!geometry) throw new Error(`unknown geometry '${geometryKey}'`);
86 const lmax = Number(specAttrs.lmax);
87 const steps = Number(specAttrs.steps);
88 const niter = Number(specAttrs.niter);
89 if (!Number.isInteger(lmax) || lmax < 1) throw new Error(`bad lmax '${String(specAttrs.lmax)}'`);
90 if (!Number.isInteger(steps) || steps < 1) throw new Error(`bad steps '${String(specAttrs.steps)}'`);
91 if (!Number.isInteger(niter) || niter < 0) throw new Error(`bad niter '${String(specAttrs.niter)}'`);
92 const nlm = nlmCalc(lmax, lmax);
94 const params: Params = {
95 ...defaultParams(model),
96 ...numberAttrs(groupOf(spec, 'params')),
97 };
98 if (!(params.dt! > 0)) throw new Error(`bad dt '${params.dt}'`);
99 const geometryParams: Params = {
100 ...defaultGeometryParams(geometry),
101 ...numberAttrs(groupOf(spec, 'geometry_params')),
102 };
104 const geom = groupOf(file, 'geometry');
105 const geometryCoeffs = {
106 X: coeffsOf(geom, 'geometry', 'Gx', nlm),
107 Y: coeffsOf(geom, 'geometry', 'Gy', nlm),
108 Z: coeffsOf(geom, 'geometry', 'Gz', nlm),
109 };
111 const initialGroup = groupOf(file, 'initial');
112 const finalGroup = groupOf(file, 'final');
113 const initial: Record<string, Float32Array> = {};
114 const final: Record<string, Float32Array> = {};
115 for (const name of model.state) {
116 initial[name] = coeffsOf(initialGroup, 'initial', name, nlm);
117 final[name] = coeffsOf(finalGroup, 'final', name, nlm);
118 }
120 return {
121 label, model, geometry, params, geometryParams,
122 lmax, niter, steps, geometryCoeffs, initial, final,
123 };
moveopenescclose