Skip to content

Commit 7bbeb86

Browse files
Polish CLASS - drop BMI, update runner, add output metadata (#138)
* Implement basic zoom; TODO: fix angle in skewT * Add logarithmic zoom for skew-T diagram; TODO: T calculations should use real axis extent rather than fixes base/top pressure * Fix skew-T lines responding to original extent instead of actual; now they don't tilt anymore * Add panning effect, but it is stroboscopic and doesn't work for skewT yet * combine side-effects for both axes in a single callback * Remove animationframe * Use produce to update both scales in a single call * Don't update panstart; this fixes the jittering * Also work in log space * Make consistent for x-direction * zoom towards cursor * Add reset plot button * Thinner lines * Higher resolution plot * Round time to steps of 10 minutes * Use runClass from package, skipping BMI altogher + rich metadata for output vars; however, app/model hangs on second or third run * Fix hanging issue: wrap only once... * Make sure initial state is included in output * Reset pan/zoom when variable changes * formatting * Use output metadata in plot labels and variable pickers * ditch BMI * Fix xlabel in timeseries plot; fix axes extent for non-time on x-axis * Expose output freq + document runClass Moved docstring of bmi.ts:BmiClass:run() to runClass --------- Co-authored-by: sverhoeven <[email protected]>
1 parent ed6baff commit 7bbeb86

File tree

11 files changed

+182
-240
lines changed

11 files changed

+182
-240
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,8 @@ by [Kobalte](https://kobalte.dev/docs/core/overview/introduction) and
117117
application and tweaked further as seen fit. It can also do charts, using
118118
[chart.js](https://www.chartjs.org/), though we might deviate from that later.
119119

120-
To expose the model in a standard way we use the [Basic Model Interface (BMI)](https://bmi.readthedocs.io/).
121-
122120
To prevent the user interface from getting blocked by running the model we use a [Web worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) to run the computation in a background task/thread.
123-
A Web worker uses messages passing to communicate, this does not fit with the Basic Model Interface so we use [comlink](https://github.com/GoogleChromeLabs/comlink) to wrap the Web Worker in a BMI class.
121+
We use [comlink](https://github.com/GoogleChromeLabs/comlink) to wrap the Web Worker so it behaves the same as if the runner was used directly inside the main thread.
124122

125123
To format and lint the code, we use [biome](https://biomejs.dev/) as it combines eslint, prettier in one package.
126124

apps/class-solid/src/components/Analysis.tsx

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { BmiClass } from "@classmodel/class/bmi";
21
import type { Config } from "@classmodel/class/config";
3-
import type { ClassOutput } from "@classmodel/class/runner";
2+
import { type ClassOutput, outputVariables } from "@classmodel/class/runner";
3+
import * as d3 from "d3";
44
import { saveAs } from "file-saver";
55
import { toBlob } from "html-to-image";
66
import {
@@ -115,9 +115,15 @@ const uniqueTimes = () => [...new Set(_allTimes())].sort((a, b) => a - b);
115115

116116
// TODO: could memoize all reactive elements here, would it make a difference?
117117
export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
118-
const xVariableOptions = ["t"]; // TODO: separate plot types for timeseries and x-vs-y? Use time axis?
119-
// TODO: add nice description from config as title and dropdown option for the variable picker.
120-
const yVariableOptions = new BmiClass().get_output_var_names();
118+
const symbols = Object.fromEntries(
119+
outputVariables.map((v) => [v.key, v.symbol]),
120+
);
121+
const getKey = Object.fromEntries(
122+
outputVariables.map((v) => [v.symbol, v.key]),
123+
);
124+
const labels = Object.fromEntries(
125+
outputVariables.map((v) => [v.key, `${v.symbol} [${v.unit}]`]),
126+
);
121127

122128
const allX = () =>
123129
flatExperiments().flatMap((e) =>
@@ -128,7 +134,8 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
128134
e.output ? e.output[analysis.yVariable] : [],
129135
);
130136

131-
const xLim = () => getNiceAxisLimits(allX(), 0, 600);
137+
const granularity = () => (analysis.xVariable === "t" ? 600 : undefined);
138+
const xLim = () => getNiceAxisLimits(allX(), 0, granularity());
132139
const yLim = () => getNiceAxisLimits(allY());
133140

134141
const chartData = () =>
@@ -158,14 +165,34 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
158165
setToggles(label, value);
159166
}
160167

168+
const setXVar = (symbol: string) => {
169+
updateAnalysis(analysis, { xVariable: getKey[symbol] });
170+
setResetPlot(analysis.id);
171+
};
172+
173+
const setYVar = (symbol: string) => {
174+
updateAnalysis(analysis, { yVariable: getKey[symbol] });
175+
setResetPlot(analysis.id);
176+
};
177+
178+
const formatX = () =>
179+
analysis.xVariable === "t" ? formatSeconds : d3.format(".4");
180+
const formatY = () =>
181+
analysis.yVariable === "t" ? formatSeconds : d3.format(".4");
182+
161183
return (
162184
<>
163185
{/* TODO: get label for yVariable from model config */}
164186
<ChartContainer>
165187
<Legend entries={chartData} toggles={toggles} onChange={toggleLine} />
166-
<Chart id={analysis.id} title="Timeseries plot" formatX={formatSeconds}>
167-
<AxisBottom domain={xLim} label="Time [s]" />
168-
<AxisLeft domain={yLim} label={analysis.yVariable} />
188+
<Chart
189+
id={analysis.id}
190+
title="Timeseries plot"
191+
formatX={formatX}
192+
formatY={formatY}
193+
>
194+
<AxisBottom domain={xLim} label={labels[analysis.xVariable]} />
195+
<AxisLeft domain={yLim} label={labels[analysis.yVariable]} />
169196
<For each={chartData()}>
170197
{(d) => (
171198
<Show when={toggles[d.label]}>
@@ -177,15 +204,15 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
177204
</ChartContainer>
178205
<div class="flex justify-around">
179206
<Picker
180-
value={() => analysis.xVariable}
181-
setValue={(v) => updateAnalysis(analysis, { xVariable: v })}
182-
options={xVariableOptions}
207+
value={() => symbols[analysis.xVariable]}
208+
setValue={(v) => setXVar(v)}
209+
options={Object.values(symbols)}
183210
label="x-axis"
184211
/>
185212
<Picker
186-
value={() => analysis.yVariable}
187-
setValue={(v) => updateAnalysis(analysis, { yVariable: v })}
188-
options={yVariableOptions}
213+
value={() => symbols[analysis.yVariable]}
214+
setValue={(v) => setYVar(v)}
215+
options={Object.values(symbols)}
189216
label="y-axis"
190217
/>
191218
</div>
@@ -261,6 +288,11 @@ export function VerticalProfilePlot({
261288
setToggles(label, value);
262289
}
263290

291+
function changeVar(v: string) {
292+
updateAnalysis(analysis, { variable: v });
293+
setResetPlot(analysis.id);
294+
}
295+
264296
return (
265297
<>
266298
<div class="flex flex-col gap-2">
@@ -291,7 +323,7 @@ export function VerticalProfilePlot({
291323
</ChartContainer>
292324
<Picker
293325
value={() => analysis.variable}
294-
setValue={(v) => updateAnalysis(analysis, { variable: v })}
326+
setValue={(v) => changeVar(v)}
295327
options={Object.keys(variableOptions)}
296328
label="variable: "
297329
/>
@@ -307,7 +339,7 @@ export function VerticalProfilePlot({
307339

308340
type PickerProps = {
309341
value: Accessor<string>;
310-
setValue: Setter<string>;
342+
setValue: (value: string) => void;
311343
options: string[];
312344
label?: string;
313345
};

apps/class-solid/src/components/plots/ChartContainer.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ export function Chart(props: {
119119
children: JSX.Element;
120120
id: string;
121121
title?: string;
122-
formatX?: (value: number) => string;
123-
formatY?: (value: number) => string;
122+
formatX?: () => (value: number) => string;
123+
formatY?: () => (value: number) => string;
124124
transformX?: (x: number, y: number, scaleY: SupportedScaleTypes) => number;
125125
}) {
126126
const [hovering, setHovering] = createSignal(false);
@@ -142,12 +142,17 @@ export function Chart(props: {
142142
}
143143
});
144144

145-
if (props.formatX) {
146-
updateChart("formatX", () => props.formatX);
147-
}
148-
if (props.formatY) {
149-
updateChart("formatY", () => props.formatY);
150-
}
145+
createEffect(() => {
146+
if (props.formatX) {
147+
updateChart("formatX", () => props.formatX?.());
148+
}
149+
});
150+
createEffect(() => {
151+
if (props.formatY) {
152+
updateChart("formatY", () => props.formatY?.());
153+
}
154+
});
155+
151156
if (props.transformX) {
152157
updateChart("transformX", () => props.transformX);
153158
}

apps/class-solid/src/components/plots/skewTlogP.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ export function SkewTPlot(props: {
192192
<Chart
193193
id={props.id}
194194
title="Thermodynamic diagram"
195-
formatX={d3.format(".0d")}
196-
formatY={d3.format(".0d")}
195+
formatX={() => d3.format(".0d")}
196+
formatY={() => d3.format(".0d")}
197197
transformX={getTempAtCursor}
198198
>
199199
<AxisBottom

apps/class-solid/src/lib/runner.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
1-
import { BmiClass } from "@classmodel/class/bmi";
21
import type { Config } from "@classmodel/class/config";
3-
import type { ClassOutput } from "@classmodel/class/runner";
4-
import { parse } from "@classmodel/class/validate";
2+
import type { ClassOutput, runClass } from "@classmodel/class/runner";
53
import { wrap } from "comlink";
64

75
const worker = new Worker(new URL("./worker.ts", import.meta.url), {
86
type: "module",
97
});
10-
export const AsyncBmiClass = wrap<typeof BmiClass>(worker);
118

12-
export async function runClass(config: Config): Promise<ClassOutput> {
9+
const asyncRunner = wrap<typeof runClass>(worker);
10+
11+
export async function runClassAsync(config: Config): Promise<ClassOutput> {
1312
try {
14-
const parsedConfig: Config = parse(config);
15-
const model = await new AsyncBmiClass();
16-
await model.initialize(parsedConfig);
17-
const output = await model.run({
18-
var_names: new BmiClass().get_output_var_names(),
19-
});
13+
const output = asyncRunner(config);
2014
return output;
2115
} catch (error) {
2216
console.error({ config, error });

apps/class-solid/src/lib/store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { decodeAppState } from "./encode";
1212
import { parseExperimentConfig } from "./experiment_config";
1313
import type { ExperimentConfig } from "./experiment_config";
1414
import { findPresetByName } from "./presets";
15-
import { runClass } from "./runner";
15+
import { runClassAsync } from "./runner";
1616

1717
interface ExperimentOutput {
1818
reference?: ClassOutput;
@@ -38,7 +38,7 @@ export async function runExperiment(id: number) {
3838

3939
// Run reference
4040
const referenceConfig = unwrap(exp.config.reference);
41-
const newOutput = await runClass(referenceConfig);
41+
const newOutput = await runClassAsync(referenceConfig);
4242

4343
setExperiments(id, "output", "reference", newOutput);
4444

@@ -50,7 +50,7 @@ export async function runExperiment(id: number) {
5050
referenceConfig,
5151
permConfig,
5252
) as Config;
53-
const newOutput = await runClass(combinedConfig);
53+
const newOutput = await runClassAsync(combinedConfig);
5454
setExperiments(id, "output", "permutations", permCounter, newOutput);
5555
permCounter++;
5656
}

apps/class-solid/src/lib/worker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BmiClass } from "@classmodel/class/bmi";
1+
import { runClass } from "@classmodel/class/runner";
22
import { expose } from "comlink";
33

4-
expose(BmiClass);
4+
expose(runClass);

packages/class/package.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,6 @@
3636
"types": "./dist/runner.d.ts"
3737
}
3838
},
39-
"./bmi": {
40-
"import": {
41-
"default": "./dist/bmi.js",
42-
"types": "./dist/bmi.d.ts"
43-
}
44-
},
4539
"./validate": {
4640
"import": {
4741
"default": "./dist/validate.js",

packages/class/src/bmi.test.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)