/ proof-of-concept / plate-mechanics-sim
Sign in
proof-of-concept / plate-mechanics-sim
plate-mechanics-sim / apps / tutorials / src / embeds / CflExperiment.tsx
86 lines · 3.7 KBBlameHistoryRaw
1import { useEffect, useMemo, useState } from "react";
2import {
3 FD_METHOD,
4 buildPlanBundle,
5 buildRunBundle,
6 parseFdPlan,
7 parsePlateResult,
8 withEdits,
9} from "@plate-lab/core";
10import { CflSlider, CodeView, FieldView, FramePlayer, LinePlot, RunStatus, useFramePlayer, useSolve } from "@plate-lab/components";
11import { tutorialSpec } from "./tutorialSpec";
13/**
14 * Turn the time step past the stability limit and watch the scheme blow up.
15 * The FD source is shown and editable; the run uses the edited text.
16 */
17export function CflExperiment() {
18 const [cfl, setCfl] = useState(0.9);
19 const [edits, setEdits] = useState<Record<string, string>>({});
20 const [showCode, setShowCode] = useState(false);
21 const spec = useMemo(() => {
22 const s = tutorialSpec();
23 s.fd.cfl = cfl;
24 return s;
25 }, [cfl]);
26 const bundle = useMemo(() => withEdits(buildRunBundle(spec, FD_METHOD), edits), [spec, edits]);
27 const solve = useSolve(parsePlateResult);
28 const plan = useSolve(parseFdPlan);
29 useEffect(() => {
30 const pb = buildPlanBundle(spec, FD_METHOD);
31 if (pb) void plan.run({ bundle: pb });
32 // eslint-disable-next-line react-hooks/exhaustive-deps
33 }, [spec.fd.cfl]);
35 const result = solve.last;
36 const nf = result?.frames.t.length ?? 0;
37 const player = useFramePlayer(nf, 12, true);
38 const absW = useMemo(() => (result ? Float64Array.from(result.probe.w, (v) => Math.abs(v)) : null), [result]);
40 return (
41 <div className="embed">
42 <div className="embed-title">Experiment: the time step against the stability limit</div>
43 <CflSlider value={cfl} onChange={setCfl} dtMax={plan.last?.dt_max} />
44 <div className="pl-row" style={{ margin: "8px 0" }}>
45 <button className="pl-btn pl-btn-primary" onClick={() => void solve.run({ bundle })} disabled={solve.state.status === "running"}>
46 Run finite differences
47 </button>
48 <button className="pl-btn" onClick={() => setShowCode((s) => !s)}>
49 {showCode ? "Hide" : "Show"} the source that runs
50 </button>
51 {plan.last && (
52 <span className="pl-muted pl-small">
53 {plan.last.nx} × {plan.last.ny} grid, {plan.last.nsteps} steps
54 </span>
55 )}
56 </div>
57 {showCode && (
58 <CodeView files={bundle.files} edits={edits} onEditsChange={setEdits} order={["main.m", "plate_fd.m", "plate_fd_plan.m"]} initialFile="plate_fd_plan.m" />
59 )}
60 {result && (
61 <div style={{ display: "grid", gap: 8, marginTop: 8 }}>
62 <FieldView frames={result.frames.w} frameIndex={player.index} Lx={spec.plate.Lx} Ly={spec.plate.Ly} title={result.info.stable ? "Displacement" : `Displacement until the blow-up at step ${result.info.diverged_step}`} unit="µm" scale={1e6} symmetric range="frame" width={420} />
63 <FramePlayer {...player} times={result.frames.t} />
64 <LinePlot
65 title="|w| at the probe, logarithmic"
66 series={[{ x: result.t, y: absW!, label: "|w|" }]}
67 xScale={1000}
68 xLabel="time [ms]"
69 yLabel="|w| [m]"
70 logY
71 height={160}
72 />
73 <div className="pl-small pl-muted">
74 {result.info.stable
75 ? "Stable: the amplitude stays bounded and decays through the damping terms."
76 : "Unstable: the shortest wave the grid can hold is amplified by a fixed factor every step, so |w| grows exponentially until the run stops itself."}
77 </div>
78 </div>
79 )}
80 <details style={{ marginTop: 8 }}>
81 <summary className="pl-small">Run details</summary>
82 <RunStatus state={solve.state} result={result} method={FD_METHOD} onCancel={solve.cancel} />
83 </details>
84 </div>
85 );
↑↓ move↵openescclose