5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 1// Convergence test: run both solvers on the official instances through
2// numbl in node and check that errors behave as the theory says they
3// should. Run with: npx tsx test/solver-test.ts
5import { readFileSync } from "fs";
6import { fileURLToPath } from "url";
7import { dirname, join } from "path";
8import { INSTANCES, getInstance } from "../src/problems/laplace2d/spec";
9import { SOLVERS } from "../src/solvers";
10import { runPoint, type MatlabSources } from "../src/harness/runner";
11import { runSweep } from "../src/harness/sweep";
13const root = join(dirname(fileURLToPath(import.meta.url)), "..");
14const read = (p: string) => readFileSync(join(root, p), "utf-8");
16const base = {
17 buildProblem: read("src/problems/laplace2d/matlab/build_problem.m"),
18 bdata: read("src/problems/laplace2d/matlab/laplace2d_bdata.m"),
3677e1fAdd star-branch instance: branch-point data family alongside the log-charge familyJeremy Magland 19 bdataBranch: read("src/problems/laplace2d/matlab/laplace2d_bdata_branch.m"),
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 20};
21const solverSources: Record<string, MatlabSources> = {
22 mfs: { ...base, solver: read("src/solvers/mfs/solver.m") },
23 "nystrom-dlp": { ...base, solver: read("src/solvers/nystrom-dlp/solver.m") },
24};
26// Best relMax each solver must reach over its full sweep. On star-hard,
27// MFS is additionally required NOT to do well: its charge curve lies
28// beyond the data's singularities there, and if it suddenly reached high
29// accuracy the instance would no longer be testing what the spec says.
30const mustReach: Record<string, Record<string, number>> = {
3677e1fAdd star-branch instance: branch-point data family alongside the log-charge familyJeremy Magland 31 mfs: {
32 "disk-easy": 1e-12,
33 "star-medium": 1e-12,
34 "star-hard": 1e-2,
35 "star-branch": 1e-10,
36 },
37 "nystrom-dlp": {
38 "disk-easy": 1e-10,
39 "star-medium": 1e-10,
40 "star-hard": 1e-8,
41 "star-branch": 1e-10,
42 },
5127df5fastandaccurate: PDE solver benchmarks, laplace-dirichlet-2d with MFS and Nystrom DLP solversJeremy Magland 43};
44const mustNotReach: Record<string, Record<string, number>> = {
45 mfs: { "star-hard": 1e-8 },
46};
48let failures = 0;
50for (const inst of INSTANCES) {
51 for (const solver of SOLVERS) {
52 console.log(`\n== ${inst.id} / ${solver.id}`);
53 console.log(" n relMax relL2 solve(s) cold(s)");
54 let best = Infinity;
55 runSweep({
56 instance: inst,
57 solver,
58 sources: solverSources[solver.id],
59 repeats: 1,
60 onPoint: (p) => {
61 best = Math.min(best, p.relMax);
62 console.log(
63 ` ${String(p.n).padStart(4)} ${p.relMax.toExponential(3)} ` +
64 `${p.relL2.toExponential(3)} ${p.solveSeconds.toFixed(4)} ${p.coldSeconds.toFixed(4)}`
65 );
66 },
67 });
68 const reach = mustReach[solver.id][inst.id];
69 const notReach = mustNotReach[solver.id]?.[inst.id];
70 if (best > reach) {
71 console.log(` FAIL: best relMax ${best.toExponential(2)} > ${reach}`);
72 failures++;
73 } else if (notReach !== undefined && best < notReach) {
74 console.log(
75 ` FAIL: best relMax ${best.toExponential(2)} < ${notReach} ` +
76 "(instance no longer defeats this method)"
77 );
78 failures++;
79 } else {
80 console.log(` ok (best relMax ${best.toExponential(2)})`);
81 }
82 }
83}
85// The grid path: one run with wantGrid on the medium instance.
86{
87 const p = runPoint({
88 instance: getInstance("star-medium"),
89 n: 64,
90 repeats: 1,
91 wantGrid: true,
92 sources: solverSources["nystrom-dlp"],
93 });
94 if (!p.uGrid || p.uGrid.length !== 200 * 200) {
95 console.log(`\nFAIL: grid has ${p.uGrid?.length ?? 0} values, expected 40000`);
96 failures++;
97 } else {
98 console.log(`\ngrid ok (${p.uGrid.length} values)`);
99 }
100}
102if (failures > 0) {
103 console.error(`\n${failures} failure(s)`);
104 process.exit(1);
105}
106console.log("\nall checks passed");