1import type { BackendInfo, MethodDefinition, PlateResult } from "@plate-lab/core";
2import type { SolveState } from "../hooks/useSolve";
4export interface RunStatusProps {
5 state: SolveState<unknown>;
6 result?: PlateResult | null;
7 method?: MethodDefinition;
8 backend?: BackendInfo | null;
9 /** Predicted wall time for the next run [ms], shown while idle */
10 estimateMs?: number;
11 onCancel?: () => void;
12}
14/** Makes the execution visible: what ran, where, how big it was and how long it took. */
15export function RunStatus({ state, result, method, backend, estimateMs, onCancel }: RunStatusProps) {
16 const be = state.status === "done" || state.status === "error" ? state.run.backend : backend;
17 const wallMs = state.status === "done" || state.status === "error" ? state.run.wallMs : null;
18 return (
19 <div className="pl-status">
20 <div className="pl-row">
21 {state.status === "idle" && <span className="pl-badge pl-badge-info">ready</span>}
22 {state.status === "running" && (
23 <>
24 <span className="pl-badge pl-badge-info">running</span>
25 {onCancel && (
26 <button className="pl-btn pl-btn-danger" onClick={onCancel}>
27 Cancel
28 </button>
29 )}
30 </>
31 )}
32 {state.status === "done" && <span className="pl-badge pl-badge-ok">finished</span>}
33 {state.status === "error" && <span className="pl-badge pl-badge-error">{state.run.cancelled ? "cancelled" : "error"}</span>}
34 {result && !result.info.stable && <span className="pl-badge pl-badge-error">diverged at step {result.info.diverged_step}</span>}
35 {method && <span className="pl-muted">{method.name}</span>}
36 </div>
37 {state.status === "running" && (
38 <div className="pl-progress">
39 <div />
40 </div>
41 )}
42 {state.status === "error" && <div className="pl-error">{state.run.error}</div>}
43 <table>
44 <tbody>
45 <tr>
46 <td>Backend</td>
47 <td>
48 {be ? `${be.engine} ${be.version}, ${be.optimization === "1" ? "JS JIT (opt 1)" : "interpreter (opt 0)"}, ${be.host === "web-worker" ? "Web Worker in this browser" : "Node.js"}` : "numbl (not yet started)"}
49 </td>
50 </tr>
51 <tr>
52 <td>Wall time</td>
53 <td>
54 {wallMs !== null ? (
55 <>
56 {fmtMs(wallMs)} in numbl
57 {result ? ` (${fmtMs(result.info.wall_s * 1000)} inside the solver by tic/toc)` : ""}
58 </>
59 ) : state.status === "running" ? (
60 "…"
61 ) : estimateMs !== undefined ? (
62 `about ${fmtMs(estimateMs)} expected`
63 ) : (
64 "–"
65 )}
66 </td>
67 </tr>
68 {result && (
69 <tr>
70 <td>Problem size</td>
71 <td>{problemSize(result)}</td>
72 </tr>
73 )}
74 {result && (
75 <tr>
76 <td>Time step</td>
77 <td>
78 {fmtTime(result.info.dt)}, {result.info.nsteps.toLocaleString()} steps, {result.t.length.toLocaleString()} samples at the probe
79 </td>
80 </tr>
81 )}
82 </tbody>
83 </table>
84 {result && result.info.notes.length > 0 && (
85 <div>
86 {result.info.notes.map((n, i) => (
87 <div className="pl-note" key={i}>
88 {n}
89 </div>
90 ))}
91 </div>
92 )}
93 {(state.status === "running" || state.status === "done" || state.status === "error") && state.output.length > 0 && (
94 <details>
95 <summary className="pl-small pl-muted">Script output ({state.output.length} lines)</summary>
96 <div className="pl-log">{state.output.join("")}</div>
97 </details>
98 )}
99 </div>
100 );
101}
103function problemSize(r: PlateResult): string {
104 const x = r.info.extra;
105 if (r.method === "modal") return `${x.nmodes} modes, ${r.info.ncells.toLocaleString()} display points, ${r.frames.t.length} frames`;
106 if (r.method === "fd")
107 return `${x.nx} × ${x.ny} = ${r.info.ncells.toLocaleString()} nodes, ${Number(x.cell_steps).toLocaleString()} cell-steps, ${r.frames.t.length} frames`;
108 if (r.method === "implicit")
109 return `${x.nx} × ${x.ny} grid, ${Number(x.nunknowns).toLocaleString()} unknowns, Cholesky factor with ${Number(x.nnz_factor).toLocaleString()} nonzeros in ${(Number(x.factor_s) * 1000).toFixed(0)} ms, ${r.frames.t.length} frames`;
110 return `${r.info.ncells.toLocaleString()} cells`;
111}
113export function fmtMs(ms: number): string {
114 if (ms < 1000) return `${ms.toFixed(0)} ms`;
115 return `${(ms / 1000).toFixed(2)} s`;
116}
118export function fmtTime(s: number): string {
119 if (s >= 1) return `${s.toPrecision(3)} s`;
120 if (s >= 1e-3) return `${(s * 1e3).toPrecision(3)} ms`;
121 return `${(s * 1e6).toPrecision(3)} µs`;
122}