/ concept-collection / fastandaccurate
Sign in
concept-collection / fastandaccurate
fastandaccurate / src / app / components / SolutionSection.tsx
150 lines · 4.9 KBBlameHistoryRaw
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 {
8 evalPoints,
9 exactU,
10 vizGrid,
11 VIZ_NGRID,
12} from "../../problems/laplace2d/exact";
13import { SOLVERS, getSolver } from "../../solvers";
14import { solutionInBrowser } from "../workerClient";
15import type { ResultPoint } from "../../harness/resultSchema";
16import { FieldView, fieldAbsMax } from "./FieldView";
18function exactGridValues(inst: Laplace2dInstance): Float64Array {
19 const { xs } = vizGrid(inst);
20 const out = new Float64Array(VIZ_NGRID * VIZ_NGRID);
21 for (let ix = 0; ix < VIZ_NGRID; ix++) {
22 for (let iy = 0; iy < VIZ_NGRID; iy++) {
23 out[ix * VIZ_NGRID + iy] = exactU(inst, xs[ix], xs[iy]);
24 }
25 }
26 return out;
29interface Computed {
30 solverId: string;
31 n: number;
32 uGrid: Float64Array;
33 point: ResultPoint;
36export function SolutionSection({ inst }: { inst: Laplace2dInstance }) {
37 const [solverId, setSolverId] = useState(SOLVERS[0].id);
38 const [n, setN] = useState<number>(
39 SOLVERS[0].sweepN[Math.floor(SOLVERS[0].sweepN.length * 0.7)]
40 );
41 const [busy, setBusy] = useState(false);
42 const [error, setError] = useState<string | null>(null);
43 const [computed, setComputed] = useState<Computed | null>(null);
45 const exact = useMemo(() => exactGridValues(inst), [inst]);
47 const errField = useMemo(() => {
48 if (!computed) return null;
49 const out = new Float64Array(exact.length);
50 for (let i = 0; i < exact.length; i++) {
51 out[i] = Math.abs(computed.uGrid[i] - exact[i]);
52 }
53 return out;
54 }, [computed, exact]);
56 const solver = getSolver(solverId);
57 const showsComputed = computed !== null && computed.solverId === solverId;
59 // One color scale shared by the exact and computed solution fields, set
60 // by the dynamic range of the exact solution; a computed field that
61 // exceeds it clips.
62 const sharedRange = useMemo(() => {
63 const vabs = fieldAbsMax(inst, exact);
64 return { lo: -vabs, hi: vabs };
65 }, [inst, exact]);
67 const marks = useMemo(() => evalPoints(inst), [inst]);
69 async function compute() {
70 setBusy(true);
71 setError(null);
72 try {
73 const { point, uGrid } = await solutionInBrowser(inst.id, solverId, n);
74 setComputed({ solverId, n, uGrid, point });
75 } catch (err) {
76 setError(err instanceof Error ? err.message : String(err));
77 } finally {
78 setBusy(false);
79 }
80 }
82 return (
83 <div>
84 <div className="row" style={{ alignItems: "center", gap: 12, marginBottom: 12 }}>
85 <label>
86 solver{" "}
87 <select
88 value={solverId}
89 onChange={(e) => {
90 const id = e.target.value;
91 setSolverId(id);
92 const sw = getSolver(id).sweepN;
93 setN(sw[Math.floor(sw.length * 0.7)]);
94 }}
95 >
96 {SOLVERS.map((s) => (
97 <option key={s.id} value={s.id}>
98 {s.name}
99 </option>
100 ))}
101 </select>
102 </label>
103 <label>
104 n{" "}
105 <select value={n} onChange={(e) => setN(parseInt(e.target.value, 10))}>
106 {solver.sweepN.map((v) => (
107 <option key={v} value={v}>
108 {v}
109 </option>
110 ))}
111 </select>
112 </label>
113 <button className="primary" onClick={compute} disabled={busy}>
114 {busy ? "computing…" : "Compute in this browser"}
115 </button>
116 </div>
117 {error && <p className="small" style={{ color: "var(--series-2)" }}>{error}</p>}
118 <div className="row">
119 <FieldView
120 inst={inst}
121 values={exact}
122 mode="diverging"
123 range={sharedRange}
124 title="Exact solution"
125 caption="The exact solution on the visualization grid. The color scale is shared with the computed field."
126 />
127 {showsComputed && computed && (
128 <FieldView
129 inst={inst}
130 values={computed.uGrid}
131 mode="diverging"
132 range={sharedRange}
133 title={`${solver.name}, n = ${computed.n}`}
134 caption={`Computed in this browser: rel max error ${computed.point.relMax.toExponential(2)} at the evaluation points, solve ${(computed.point.solveSeconds * 1000).toPrecision(3)} ms. Same color scale as the exact solution.`}
135 />
136 )}
137 {showsComputed && errField && computed && (
138 <FieldView
139 inst={inst}
140 values={errField}
141 mode="logmag"
142 overlayPoints={marks}
143 title="Pointwise error (log scale)"
144 caption="Absolute difference from the exact solution; the color scale spans 8 decades below the maximum. Dots mark the 65 evaluation points where the reported errors are measured."
145 />
146 )}
147 </div>
148 </div>
149 );
moveopenescclose