1// The result-file format committed to the fastandaccurate-results repo.
2// One file holds one work-precision sweep: one problem instance, one
3// solver, one environment.
5import {
6 canonicalSpec,
7 canonicalSpecJson,
8 PROBLEM_ID,
9 PROBLEM_VERSION,
10 type Laplace2dInstance,
11} from "../problems/laplace2d/spec";
12import type { RunPoint } from "./runner";
14export const RESULT_FORMAT = "fastandaccurate-result";
15export const RESULT_FORMAT_VERSION = 1;
17export interface ResultEnvironment {
18 kind: "browser" | "node" | "matlab";
19 /** User agent (browser), node version (node), or MATLAB version. */
20 runtime: string;
21 /** Absent for runs outside numbl (e.g. real MATLAB). */
22 numblVersion?: string;
23 os?: string;
24 cpu?: string;
25 /** Free-text label a human recognizes ("office workstation"). */
26 machineLabel?: string;
27 /** Whether a visitor can rerun this result in the browser. */
28 browserReproducible: boolean;
29}
31export interface ResultPoint {
32 n: number;
33 solveSeconds: number;
34 solveSecondsAll: number[];
35 coldSeconds: number;
36 relMax: number;
37 relL2: number;
38}
40export interface ResultFile {
41 format: typeof RESULT_FORMAT;
42 formatVersion: typeof RESULT_FORMAT_VERSION;
43 problem: string;
44 problemVersion: number;
45 instance: string;
46 spec: Record<string, unknown>;
47 specHash: string;
48 solver: {
49 id: string;
50 version: string;
51 backend: "cpu" | "gpu";
52 /** "builtin" for solvers in the fastandaccurate repo; otherwise a
53 * URL or free-text pointer to the solver's source. */
54 source: string;
55 };
56 environment: ResultEnvironment;
57 protocol: {
58 warmupRuns: number;
59 timedRuns: number;
60 timer: string;
61 };
62 createdUtc: string;
63 points: ResultPoint[];
64}
66/** SHA-256 hex digest, using WebCrypto (browser, worker, node >= 20). */
67export async function sha256Hex(text: string): Promise<string> {
68 const digest = await crypto.subtle.digest(
69 "SHA-256",
70 new TextEncoder().encode(text)
71 );
72 return Array.from(new Uint8Array(digest))
73 .map((b) => b.toString(16).padStart(2, "0"))
74 .join("");
75}
77export function toResultPoint(p: RunPoint): ResultPoint {
78 return {
79 n: p.n,
80 solveSeconds: p.solveSeconds,
81 solveSecondsAll: p.solveSecondsAll,
82 coldSeconds: p.coldSeconds,
83 relMax: p.relMax,
84 relL2: p.relL2,
85 };
86}
88export async function buildResultFile(opts: {
89 instance: Laplace2dInstance;
90 solver: { id: string; version: string; backend: "cpu" | "gpu"; source: string };
91 environment: ResultEnvironment;
92 repeats: number;
93 points: ResultPoint[];
94 /** What measured the times (default numbl tic/toc). */
95 timer?: string;
96}): Promise<ResultFile> {
97 return {
98 format: RESULT_FORMAT,
99 formatVersion: RESULT_FORMAT_VERSION,
100 problem: PROBLEM_ID,
101 problemVersion: PROBLEM_VERSION,
102 instance: opts.instance.id,
103 spec: canonicalSpec(opts.instance) as unknown as Record<string, unknown>,
104 specHash: await sha256Hex(canonicalSpecJson(opts.instance)),
105 solver: opts.solver,
106 environment: opts.environment,
107 protocol: {
108 warmupRuns: 1,
109 timedRuns: opts.repeats,
110 timer: opts.timer ?? "numbl tic/toc",
111 },
112 createdUtc: new Date().toISOString(),
113 points: opts.points,
114 };
115}