ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 1// Convergence test: run the numbl solvers on the official instances
2// through numbl in node and check that errors behave as the theory says
3// they should. The MATLAB-runtime solvers are covered by
4// test/matlab-test.ts, run locally where MATLAB exists, against the same
5// expectations in test/expected.ts.
6// Run with: npx tsx test/solver-test.ts
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 7
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 8import { existsSync, readFileSync } from "fs";
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 9import { fileURLToPath } from "url";
10import { dirname, join } from "path";
11import { INSTANCES, getInstance } from "../src/problems/laplace2d/spec";
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 12import {
13 SOLVERS,
14 getSolver,
acdea26Add mfs-gpu: the MFS on WebGPU, and a TypeScript form of the solver interfaceJeremy Magland 15 solverFiles,
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 16 solverSourceDir,
17 type SolverManifest,
18} from "../src/solvers";
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 19import { runPoint, type MatlabSources } from "../src/harness/runner";
20import { runSweep } from "../src/harness/sweep";
59058e5Add chunkie-dlp solver: mip package support in the harness, curl-backed file I/O for nodeJeremy Magland 21import { setNumblFileIO } from "../src/harness/numblRun";
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 22import { DEFAULT_TIMING } from "../src/harness/timing";
59058e5Add chunkie-dlp solver: mip package support in the harness, curl-backed file I/O for nodeJeremy Magland 23import { NodeFileIOAdapter } from "../src/cli/nodeFileIO";
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 24import { MUST_NOT_REACH, MUST_REACH } from "./expected";
59058e5Add chunkie-dlp solver: mip package support in the harness, curl-backed file I/O for nodeJeremy Magland 25
26// Solvers that mip-install packages (chunkie-dlp) need file I/O; in node
27// that is the curl-backed adapter.
28setNumblFileIO((vfs) => new NodeFileIOAdapter(vfs));
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 29
30const root = join(dirname(fileURLToPath(import.meta.url)), "..");
31const read = (p: string) => readFileSync(join(root, p), "utf-8");
33const base = {
34 buildProblem: read("src/problems/laplace2d/matlab/build_problem.m"),
35 bdata: read("src/problems/laplace2d/matlab/laplace2d_bdata.m"),
36};
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 37const solverSources = (solver: SolverManifest): MatlabSources => ({
38 ...base,
39 solver: read(`src/solvers/${solverSourceDir(solver)}/solver.m`),
40});
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 41
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 42// The suite checks accuracy, not speed, so one timed run per point is
43// enough; the committed results are what the full timing policy is for.
44const TEST_TIMING = { ...DEFAULT_TIMING, minTimedRuns: 1, timeBudgetSeconds: 0 };
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 45
46let failures = 0;
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 48// Registry consistency, checked for every entry including the ones this
acdea26Add mfs-gpu: the MFS on WebGPU, and a TypeScript form of the solver interfaceJeremy Magland 49// suite does not run: the solver's source files must exist, it must have
50// stated expectations, and an entry that borrows another entry's source
51// must carry the same version, so that two results claiming the same solver
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 52// version really did run the same code.
53for (const s of SOLVERS) {
54 const dir = solverSourceDir(s);
acdea26Add mfs-gpu: the MFS on WebGPU, and a TypeScript form of the solver interfaceJeremy Magland 55 for (const file of solverFiles(s)) {
56 const path = `src/solvers/${file}`;
57 if (!existsSync(join(root, path))) {
58 console.log(`FAIL: ${s.id} has no ${path}`);
59 failures++;
60 }
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 61 }
62 const expected = MUST_REACH[dir];
63 if (!expected) {
64 console.log(`FAIL: ${s.id} has no entry in test/expected.ts`);
65 failures++;
acdea26Add mfs-gpu: the MFS on WebGPU, and a TypeScript form of the solver interfaceJeremy Magland 66 } else if (s.runtime === "numbl" || s.runtime === "webgpu") {
67 // Both of these run every instance, in test/solver-test.ts and
68 // test/gpu-test.ts respectively.
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 69 for (const inst of INSTANCES) {
70 if (expected[inst.id] === undefined) {
71 console.log(`FAIL: ${s.id} has no expectation on ${inst.id}`);
72 failures++;
73 }
74 }
75 }
76 const twin = s.sourceDir && SOLVERS.find((x) => x.id === s.sourceDir);
77 if (s.sourceDir && !twin) {
78 console.log(
79 `FAIL: ${s.id} names sourceDir ${s.sourceDir}, which is not a solver`
80 );
81 failures++;
82 } else if (twin && twin.version !== s.version) {
83 console.log(
84 `FAIL: ${s.id} v${s.version} shares solver.m with ${twin.id} v${twin.version}`
85 );
86 failures++;
87 }
88}
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 90for (const inst of INSTANCES) {
e58e208chunkie-dlp runs in real MATLAB via the CLI; solver runtime field, matlab -batch harnessJeremy Magland 91 for (const solver of SOLVERS.filter((s) => s.runtime === "numbl")) {
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 92 console.log(`\n== ${inst.id} / ${solver.id}`);
93 console.log(" n relMax relL2 solve(s) cold(s)");
94 let best = Infinity;
95 runSweep({
96 instance: inst,
97 solver,
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 98 sources: solverSources(solver),
99 timing: TEST_TIMING,
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 100 onPoint: (p) => {
101 best = Math.min(best, p.relMax);
102 console.log(
103 ` ${String(p.n).padStart(4)} ${p.relMax.toExponential(3)} ` +
104 `${p.relL2.toExponential(3)} ${p.solveSeconds.toFixed(4)} ${p.coldSeconds.toFixed(4)}`
105 );
106 },
107 });
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 108 const dir = solverSourceDir(solver);
109 const reach = MUST_REACH[dir][inst.id];
110 const notReach = MUST_NOT_REACH[dir]?.[inst.id];
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 111 if (best > reach) {
112 console.log(` FAIL: best relMax ${best.toExponential(2)} > ${reach}`);
113 failures++;
114 } else if (notReach !== undefined && best < notReach) {
115 console.log(
116 ` FAIL: best relMax ${best.toExponential(2)} < ${notReach} ` +
117 "(instance no longer defeats this method)"
118 );
119 failures++;
120 } else {
121 console.log(` ok (best relMax ${best.toExponential(2)})`);
122 }
123 }
124}
126// The grid path: one run with wantGrid on the medium instance.
127{
128 const p = runPoint({
129 instance: getInstance("star-medium"),
130 n: 64,
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 131 timing: TEST_TIMING,
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 132 wantGrid: true,
ad5dc23Three new instances, near-field target sets, and an adaptive timing policyJeremy Magland 133 sources: solverSources(getSolver("nystrom-dlp")),
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 134 });
135 if (!p.uGrid || p.uGrid.length !== 200 * 200) {
136 console.log(`\nFAIL: grid has ${p.uGrid?.length ?? 0} values, expected 40000`);
137 failures++;
138 } else {
139 console.log(`\ngrid ok (${p.uGrid.length} values)`);
140 }
141}
143if (failures > 0) {
144 console.error(`\n${failures} failure(s)`);
145 process.exit(1);
146}
147console.log("\nall checks passed");