// Runs one solver at one resolution on one instance and measures it. // The timing protocol (see docs/problems/laplace-dirichlet-2d.md): two // untimed warmup runs absorb JIT compilation, then timed runs under the // policy in ./timing (a floor on the count, then as many more as fit in a // time budget), of which the fastest is reported. The minimum is the least // contaminated estimator of the solver's own cost, since everything that // interferes (scheduling, residual compilation, other load) only ever adds // time; all the individual timings are recorded too. The first warmup is // timed and reported separately as the cold time. All timing is MATLAB // tic/toc inside the numbl session, so browser and node measure the same // thing. import { runNumblScript } from "./numblRun"; import { DEFAULT_TIMING, timedRunLines, type TimingPolicy } from "./timing"; import type { Laplace2dInstance } from "../problems/laplace2d/spec"; import { evalErrors } from "../problems/laplace2d/exact"; export interface MatlabSources { /** build_problem.m source */ buildProblem: string; /** laplace2d_bdata.m source */ bdata: string; /** solver.m source of the solver under test */ solver: string; } export interface RunPointRequest { instance: Laplace2dInstance; n: number; /** How the timed runs are counted (default DEFAULT_TIMING). */ timing?: TimingPolicy; /** Also evaluate the solution on the visualization grid. */ wantGrid?: boolean; sources: MatlabSources; } export interface RunPoint { n: number; /** The fastest of the timed runs. */ solveSeconds: number; /** Every timed run, in order; its length is how many the policy ran. */ solveSecondsAll: number[]; coldSeconds: number; relMax: number; relL2: number; uEval: Float64Array; uGrid: Float64Array | null; } function numLiteral(x: number): string { if (!Number.isFinite(x)) throw new Error(`bad numeric literal: ${x}`); return String(x); } export function runPoint(req: RunPointRequest): RunPoint { const { instance, n } = req; const timing = req.timing ?? DEFAULT_TIMING; const wantGrid = req.wantGrid ?? false; const main = [ "% generated by the fastandaccurate harness", `prob = build_problem(${numLiteral(instance.a)}, ${numLiteral(instance.k)}, ` + `${numLiteral(instance.p ?? 0)}, ${numLiteral(instance.d)}, ${wantGrid ? 1 : 0}, ` + `${instance.nearBoundary ? 1 : 0});`, `tic; out = solver(prob, ${n}); res_cold = toc;`, `out = solver(prob, ${n});`, ...timedRunLines(`out = solver(prob, ${n})`, "res_times", timing), "res_ueval = out.uEval;", "res_ugrid = out.uGrid;", "", ].join("\n"); const { vars } = runNumblScript( main, { "build_problem.m": req.sources.buildProblem, "laplace2d_bdata.m": req.sources.bdata, "solver.m": req.sources.solver, }, ["res_cold", "res_times", "res_ueval", "res_ugrid"] ); const times = Array.from(vars.res_times); const { relMax, relL2 } = evalErrors(instance, vars.res_ueval); return { n, solveSeconds: Math.min(...times), solveSecondsAll: times, coldSeconds: vars.res_cold[0], relMax, relL2, uEval: vars.res_ueval, uGrid: wantGrid ? vars.res_ugrid : null, }; }