1// The solution-and-error view: compute one solve in the browser at a
2// chosen resolution and show the field next to its pointwise error
3// against the exact solution.
5import { useMemo, useState } from "react";
6import type { Laplace2dInstance } from "../../problems/laplace2d/spec";
7import { exactU, vizGrid, VIZ_NGRID } from "../../problems/laplace2d/exact";
8import { SOLVERS, getSolver } from "../../solvers";
9import { solutionInBrowser } from "../workerClient";
10import type { ResultPoint } from "../../harness/resultSchema";
11import { FieldView } from "./FieldView";
13function exactGridValues(inst: Laplace2dInstance): Float64Array {
14 const { xs } = vizGrid(inst);
15 const out = new Float64Array(VIZ_NGRID * VIZ_NGRID);
16 for (let ix = 0; ix < VIZ_NGRID; ix++) {
17 for (let iy = 0; iy < VIZ_NGRID; iy++) {
18 out[ix * VIZ_NGRID + iy] = exactU(inst, xs[ix], xs[iy]);
19 }
20 }
21 return out;
22}
24interface Computed {
25 solverId: string;
26 n: number;
27 uGrid: Float64Array;
28 point: ResultPoint;
29}
31export function SolutionSection({ inst }: { inst: Laplace2dInstance }) {
32 const [solverId, setSolverId] = useState(SOLVERS[0].id);
33 const [n, setN] = useState<number>(
34 SOLVERS[0].sweepN[Math.floor(SOLVERS[0].sweepN.length * 0.7)]
35 );
36 const [busy, setBusy] = useState(false);
37 const [error, setError] = useState<string | null>(null);
38 const [computed, setComputed] = useState<Computed | null>(null);
40 const exact = useMemo(() => exactGridValues(inst), [inst]);
42 const errField = useMemo(() => {
43 if (!computed) return null;
44 const out = new Float64Array(exact.length);
45 for (let i = 0; i < exact.length; i++) {
46 out[i] = Math.abs(computed.uGrid[i] - exact[i]);
47 }
48 return out;
49 }, [computed, exact]);
51 const solver = getSolver(solverId);
52 const showsComputed = computed !== null && computed.solverId === solverId;
54 async function compute() {
55 setBusy(true);
56 setError(null);
57 try {
58 const { point, uGrid } = await solutionInBrowser(inst.id, solverId, n);
59 setComputed({ solverId, n, uGrid, point });
60 } catch (err) {
61 setError(err instanceof Error ? err.message : String(err));
62 } finally {
63 setBusy(false);
64 }
65 }
67 return (
68 <div>
69 <div className="row" style={{ alignItems: "center", gap: 12, marginBottom: 12 }}>
70 <label>
71 solver{" "}
72 <select
73 value={solverId}
74 onChange={(e) => {
75 const id = e.target.value;
76 setSolverId(id);
77 const sw = getSolver(id).sweepN;
78 setN(sw[Math.floor(sw.length * 0.7)]);
79 }}
80 >
81 {SOLVERS.map((s) => (
82 <option key={s.id} value={s.id}>
83 {s.name}
84 </option>
85 ))}
86 </select>
87 </label>
88 <label>
89 n{" "}
90 <select value={n} onChange={(e) => setN(parseInt(e.target.value, 10))}>
91 {solver.sweepN.map((v) => (
92 <option key={v} value={v}>
93 {v}
94 </option>
95 ))}
96 </select>
97 </label>
98 <button className="primary" onClick={compute} disabled={busy}>
99 {busy ? "computing…" : "Compute in this browser"}
100 </button>
101 </div>
102 {error && <p className="small" style={{ color: "var(--series-2)" }}>{error}</p>}
103 <div className="row">
104 <FieldView
105 inst={inst}
106 values={exact}
107 mode="diverging"
108 title="Exact solution"
109 caption="The manufactured harmonic function, sampled on the visualization grid."
110 />
111 {showsComputed && computed && (
112 <FieldView
113 inst={inst}
114 values={computed.uGrid}
115 mode="diverging"
116 title={`${solver.name}, n = ${computed.n}`}
117 caption={`Computed in this browser: rel max error ${computed.point.relMax.toExponential(2)}, solve ${(computed.point.solveSeconds * 1000).toPrecision(3)} ms.`}
118 />
119 )}
120 {showsComputed && errField && computed && (
121 <FieldView
122 inst={inst}
123 values={errField}
124 mode="logmag"
125 title="Pointwise error (log scale)"
126 caption="Absolute difference from the exact solution; the color scale spans 8 decades below the maximum."
127 />
128 )}
129 </div>
130 </div>
131 );
132}