Skip to content

Commit 03f399b

Browse files
further fixes
1 parent d54588c commit 03f399b

5 files changed

Lines changed: 130 additions & 125 deletions

File tree

frontend/src/components/GridSimulation/CompareGridSimulations.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ interface CompareGridSimulationsProps {
1212
gridIds: string[];
1313
}
1414

15-
const modelChainLabel = (result: GridSimulationResult): string =>
16-
result.models.map((model) => model.modelId).join(" → ") || "Unknown model";
15+
const modelChainLabel = (result: GridSimulationResult): string => {
16+
const firstSim = result.simulations[0];
17+
if (!firstSim) return "Unknown model";
18+
return firstSim.input.models.map((m) => m.modelId).join(" → ") || "Unknown model";
19+
};
1720

1821
const CompareGridSimulations: React.FC<CompareGridSimulationsProps> = ({ gridIds }) => {
1922
const queries = useQueries({
@@ -31,9 +34,9 @@ const CompareGridSimulations: React.FC<CompareGridSimulationsProps> = ({ gridIds
3134

3235
const allSubstances = useMemo(() => {
3336
const substances = new Set<string>();
34-
results.forEach((r) => significantSubstances(r.points).forEach((s) => substances.add(s)));
37+
results.forEach((r) => significantSubstances(r.simulations).forEach((s) => substances.add(s)));
3538
if (substances.size === 0) {
36-
results.forEach((r) => collectOutputSubstances(r.points).forEach((s) => substances.add(s)));
39+
results.forEach((r) => collectOutputSubstances(r.simulations).forEach((s) => substances.add(s)));
3740
}
3841
return Array.from(substances).sort();
3942
}, [results]);
@@ -74,17 +77,18 @@ const CompareGridSimulations: React.FC<CompareGridSimulationsProps> = ({ gridIds
7477
);
7578
}
7679

80+
const xAxisSubstance = results[0]?.axes[0]?.substance ?? "";
7781
const unifiedXValues = Array.from(
78-
new Set(results.flatMap((r) => r.points.map((p) => p.coordinates[0])))
82+
new Set(results.flatMap((r) => r.simulations.map((sim) => sim.input.concentrations[xAxisSubstance] ?? 0)))
7983
).sort((a, b) => a - b);
8084

8185
const series: LineSeries[] = results.map((r) => {
82-
const valueToPoint = new Map(r.points.map((point) => [point.coordinates[0], point]));
86+
const valueToSim = new Map(r.simulations.map((sim) => [sim.input.concentrations[xAxisSubstance] ?? 0, sim]));
8387
return {
8488
label: `${modelChainLabel(r)} · ${r.axes[0]?.substance ?? ""}`,
8589
data: unifiedXValues.map((x) => {
86-
const point = valueToPoint.get(x);
87-
return point ? pointOutput(point, selectedSubstance) : null;
90+
const sim = valueToSim.get(x);
91+
return sim ? pointOutput(sim, selectedSubstance) : null;
8892
}),
8993
};
9094
});

frontend/src/components/GridSimulation/GridResults.tsx

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useMemo, useState } from "react";
22
import { Autocomplete, Banner, Table, Typography } from "@equinor/eds-core-react";
33
import { Link } from "react-router-dom";
44
import { GridSimulationResult } from "@/dto/GridSimulation";
5+
import { SimulationResults } from "@/dto/SimulationResults";
56
import { formatConcentration } from "@/functions/Formatting";
67
import { buildGridCsv, collectOutputSubstances, defaultSelectedSubstances, pointOutput } from "@/functions/GridSimulation";
78
import { optionName } from "@/functions/Substance";
@@ -13,34 +14,37 @@ interface GridResultsProps {
1314
}
1415

1516
const GridResults: React.FC<GridResultsProps> = ({ result }) => {
16-
const { points, axes, models } = result;
17+
const { simulations, axes } = result;
1718

18-
const allSubstances = useMemo(() => collectOutputSubstances(points), [points]);
19-
const [selectedSubstances, setSelectedSubstances] = useState<string[]>(() => defaultSelectedSubstances(points));
19+
const firstSim = simulations[0];
20+
const models = firstSim?.input.models ?? [];
2021

21-
const erroredPoints = points.filter((point) => point.status === "error");
22+
const allSubstances = useMemo(() => collectOutputSubstances(simulations), [simulations]);
23+
const [selectedSubstances, setSelectedSubstances] = useState<string[]>(() => defaultSelectedSubstances(simulations));
2224

23-
const xValues = points.map((point) => point.coordinates[0]);
25+
const erroredSimulations = simulations.filter((sim) => sim.status === "error");
26+
27+
const xAxisSubstance = axes[0]?.substance ?? "Unknown";
28+
const xValues = simulations.map((sim) => sim.input.concentrations[xAxisSubstance] ?? 0);
2429
const series: LineSeries[] = selectedSubstances.map((substance) => ({
2530
label: optionName(substance),
26-
data: points.map((point) => pointOutput(point, substance)),
31+
data: simulations.map((sim) => pointOutput(sim, substance)),
2732
}));
2833

2934
const modelLabel = models.map((model) => model.modelId).join(" → ") || "Unknown model";
30-
const xAxisSubstance = axes[0]?.substance ?? "Unknown";
3135

3236
return (
3337
<>
3438
<Typography variant="body_short" style={{ marginBottom: "1rem" }}>
35-
Varying <strong>{optionName(xAxisSubstance)}</strong> across {points.length} values using{" "}
39+
Varying <strong>{optionName(xAxisSubstance)}</strong> across {simulations.length} values using{" "}
3640
<strong>{modelLabel}</strong>.
3741
</Typography>
3842

39-
{erroredPoints.length > 0 && (
43+
{erroredSimulations.length > 0 && (
4044
<Banner style={{ marginBottom: "1rem" }}>
4145
<Banner.Icon variant="warning">⚠️</Banner.Icon>
4246
<Banner.Message>
43-
{erroredPoints.length} of {points.length} runs failed and are omitted from the chart.
47+
{erroredSimulations.length} of {simulations.length} runs failed and are omitted from the chart.
4448
</Banner.Message>
4549
</Banner>
4650
)}
@@ -82,27 +86,23 @@ const GridResults: React.FC<GridResultsProps> = ({ result }) => {
8286
{selectedSubstances.map((substance) => (
8387
<Table.Cell key={substance}>{optionName(substance)}</Table.Cell>
8488
))}
85-
<Table.Cell>Run</Table.Cell>
8689
</Table.Row>
8790
</Table.Head>
8891
<Table.Body>
89-
{points.map((point) => (
90-
<Table.Row key={point.simulationId}>
91-
{point.coordinates.map((coord, i) => (
92-
<Table.Cell key={i}>{coord}</Table.Cell>
92+
{simulations.map((sim, idx) => (
93+
<Table.Row key={idx}>
94+
{axes.map((axis) => (
95+
<Table.Cell key={axis.substance}>{sim.input.concentrations[axis.substance] ?? 0}</Table.Cell>
9396
))}
9497
{selectedSubstances.map((substance) => (
9598
<Table.Cell key={substance}>
96-
{point.status === "done"
97-
? formatConcentration(point.concentrations[substance])
98-
: point.status === "error"
99+
{sim.status === "done"
100+
? formatConcentration(pointOutput(sim, substance))
101+
: sim.status === "error"
99102
? "error"
100103
: "…"}
101104
</Table.Cell>
102105
))}
103-
<Table.Cell>
104-
<Link to={`/simulations/${point.simulationId}`}>open</Link>
105-
</Table.Cell>
106106
</Table.Row>
107107
))}
108108
</Table.Body>

frontend/src/components/Simulation/ModelInputs.tsx

Lines changed: 63 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { ChangeEvent, useState } from "react";
22
import { ModelConfig } from "@/dto/FormConfig";
3-
import { Autocomplete, Button, NativeSelect, TextField, Typography } from "@equinor/eds-core-react";
3+
import { Accordion, Autocomplete, Button, NativeSelect, TextField, Typography } from "@equinor/eds-core-react";
44
import { MetaTooltip } from "@/functions/Tooltip";
55
import ConvertibleTextField from "@/components/ConvertibleTextField";
66
import { Columns } from "@/components/styles";
@@ -114,28 +114,33 @@ function GridRangeInput({ candidateSubstances }: { candidateSubstances: string[]
114114
);
115115

116116
const axis = axes[0];
117+
const isActive = axis !== undefined;
118+
119+
const headerText = isActive
120+
? `Concentration range: ${optionName(axis.substance)}`
121+
: "Concentration range";
117122

118123
return (
119-
<div style={{ display: "flex", flexDirection: "column", gap: "16px", flexGrow: 1 }}>
120-
<Typography variant="h3">Concentration range</Typography>
121-
<Typography variant="body_short">
122-
Optionally vary a single concentration across a range to see how the outputs respond.
123-
</Typography>
124-
{axis ? (
125-
<>
126-
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
124+
<Accordion>
125+
<Accordion.Item isExpanded={isActive}>
126+
<Accordion.Header>
127+
{headerText}
128+
</Accordion.Header>
129+
<Accordion.Panel>
130+
<div style={{ display: "flex", flexDirection: "column", gap: "12px", padding: "4px 0" }}>
127131
<NativeSelect
128132
id="gridAxis0"
129133
label="Substance"
130-
value={axis.substance}
134+
value={axis?.substance ?? ""}
131135
onChange={(e) => {
132136
if (e.target.value === "") {
133-
removeAxis(0);
134-
} else {
137+
if (isActive) removeAxis(0);
138+
} else if (isActive) {
135139
updateAxis(0, { substance: e.target.value });
140+
} else {
141+
addAxis(e.target.value);
136142
}
137143
}}
138-
style={{ flex: 1 }}
139144
>
140145
<option value="">None (single run)</option>
141146
{candidateSubstances.map((substance) => (
@@ -144,63 +149,49 @@ function GridRangeInput({ candidateSubstances }: { candidateSubstances: string[]
144149
</option>
145150
))}
146151
</NativeSelect>
152+
{isActive && (
153+
<>
154+
<TextField
155+
id="gridMin0"
156+
type="number"
157+
label="From"
158+
unit="ppm"
159+
step="any"
160+
min={0}
161+
max={PPM_MAX}
162+
value={axis.range.min}
163+
onChange={(e: ChangeEvent<HTMLInputElement>) => updateAxis(0, { range: { ...axis.range, min: e.target.valueAsNumber } })}
164+
variant={axis.range.max > axis.range.min ? undefined : "error"}
165+
/>
166+
<TextField
167+
id="gridMax0"
168+
type="number"
169+
label="To"
170+
unit="ppm"
171+
step="any"
172+
min={0}
173+
max={PPM_MAX}
174+
value={axis.range.max}
175+
onChange={(e: ChangeEvent<HTMLInputElement>) => updateAxis(0, { range: { ...axis.range, max: e.target.valueAsNumber } })}
176+
variant={axis.range.max > axis.range.min ? undefined : "error"}
177+
helperText={axis.range.max > axis.range.min ? undefined : "'To' must be greater than 'From'"}
178+
/>
179+
<TextField
180+
id="gridSteps0"
181+
type="number"
182+
label="Number of values"
183+
step={1}
184+
min={2}
185+
max={25}
186+
value={axis.range.steps}
187+
onChange={(e: ChangeEvent<HTMLInputElement>) => updateAxis(0, { range: { ...axis.range, steps: e.target.valueAsNumber } })}
188+
/>
189+
</>
190+
)}
147191
</div>
148-
<TextField
149-
id="gridMin0"
150-
type="number"
151-
label="From"
152-
unit="ppm"
153-
step="any"
154-
min={0}
155-
max={PPM_MAX}
156-
value={axis.range.min}
157-
onChange={(e: ChangeEvent<HTMLInputElement>) => updateAxis(0, { range: { ...axis.range, min: e.target.valueAsNumber } })}
158-
variant={axis.range.max > axis.range.min ? undefined : "error"}
159-
/>
160-
<TextField
161-
id="gridMax0"
162-
type="number"
163-
label="To"
164-
unit="ppm"
165-
step="any"
166-
min={0}
167-
max={PPM_MAX}
168-
value={axis.range.max}
169-
onChange={(e: ChangeEvent<HTMLInputElement>) => updateAxis(0, { range: { ...axis.range, max: e.target.valueAsNumber } })}
170-
variant={axis.range.max > axis.range.min ? undefined : "error"}
171-
helperText={axis.range.max > axis.range.min ? undefined : "'To' must be greater than 'From'"}
172-
/>
173-
<TextField
174-
id="gridSteps0"
175-
type="number"
176-
label="Number of values"
177-
step={1}
178-
min={2}
179-
max={25}
180-
value={axis.range.steps}
181-
onChange={(e: ChangeEvent<HTMLInputElement>) => updateAxis(0, { range: { ...axis.range, steps: e.target.valueAsNumber } })}
182-
/>
183-
</>
184-
) : (
185-
<NativeSelect
186-
id="gridAxis0"
187-
label="Substance"
188-
value=""
189-
onChange={(e) => {
190-
if (e.target.value !== "") {
191-
addAxis(e.target.value);
192-
}
193-
}}
194-
>
195-
<option value="">None (single run)</option>
196-
{candidateSubstances.map((substance) => (
197-
<option key={substance} value={substance}>
198-
{optionName(substance)}
199-
</option>
200-
))}
201-
</NativeSelect>
202-
)}
203-
</div>
192+
</Accordion.Panel>
193+
</Accordion.Item>
194+
</Accordion>
204195
);
205196
}
206197

@@ -316,6 +307,9 @@ const ModelInputs: React.FC<{
316307
);
317308
})}
318309
<SubstanceAdder invisible={invisible} onAdd={(item: string) => setConcentration(item, 0)} />
310+
<div style={{ marginTop: "1rem" }}>
311+
<GridRangeInput candidateSubstances={gridableSubstances} />
312+
</div>
319313
</div>
320314
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
321315
<Typography variant="h3">Conditions</Typography>
@@ -341,7 +335,6 @@ const ModelInputs: React.FC<{
341335
{selectedModels.map((model) => (
342336
<ModelParametersWrapper key={model.modelId} model={model} />
343337
))}
344-
<GridRangeInput candidateSubstances={gridableSubstances} />
345338
</Columns>
346339
{hasInvalidSubstances && (
347340
<Typography

frontend/src/functions/GridSimulation.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,52 @@
1-
import { GridPoint, GridSimulationResult } from "@/dto/GridSimulation";
1+
import { GridSimulationResult } from "@/dto/GridSimulation";
2+
import { SimulationResults, getCo2RichConcentrations } from "@/dto/SimulationResults";
23
import { TabulatedResultRow } from "@/dto/ChartData";
34
import { convertTabulatedDataToCSVFormat } from "@/functions/Formatting";
45

56
export const SIGNIFICANCE_THRESHOLD = 0.01;
67
export const MAX_DEFAULT_SERIES = 6;
78

8-
export const pointOutput = (point: GridPoint, substance: string): number | null =>
9-
point.status === "done" ? (point.concentrations[substance] ?? 0) : null;
9+
const lastPhaseConcentrations = (sim: SimulationResults): Record<string, number> => {
10+
if (sim.status !== "done" || sim.results.length === 0) return {};
11+
const lastResult = sim.results[sim.results.length - 1];
12+
return getCo2RichConcentrations(lastResult.phases);
13+
};
14+
15+
export const pointOutput = (sim: SimulationResults, substance: string): number | null =>
16+
sim.status === "done" ? (lastPhaseConcentrations(sim)[substance] ?? 0) : null;
1017

11-
export const collectOutputSubstances = (points: GridPoint[]): string[] => {
18+
export const collectOutputSubstances = (simulations: SimulationResults[]): string[] => {
1219
const substances = new Set<string>();
13-
points.forEach((point) => {
14-
if (point.status === "done") {
15-
Object.keys(point.concentrations).forEach((substance) => substances.add(substance));
16-
}
20+
simulations.forEach((sim) => {
21+
Object.keys(lastPhaseConcentrations(sim)).forEach((s) => substances.add(s));
1722
});
1823
return Array.from(substances).sort();
1924
};
2025

21-
export const maxOutput = (points: GridPoint[], substance: string): number =>
22-
points.reduce(
23-
(currentMax, point) =>
24-
point.status === "done" ? Math.max(currentMax, point.concentrations[substance] ?? 0) : currentMax,
26+
export const maxOutput = (simulations: SimulationResults[], substance: string): number =>
27+
simulations.reduce(
28+
(currentMax, sim) =>
29+
sim.status === "done" ? Math.max(currentMax, lastPhaseConcentrations(sim)[substance] ?? 0) : currentMax,
2530
0
2631
);
2732

28-
export const significantSubstances = (points: GridPoint[]): string[] =>
29-
collectOutputSubstances(points).filter((substance) => maxOutput(points, substance) >= SIGNIFICANCE_THRESHOLD);
33+
export const significantSubstances = (simulations: SimulationResults[]): string[] =>
34+
collectOutputSubstances(simulations).filter((s) => maxOutput(simulations, s) >= SIGNIFICANCE_THRESHOLD);
3035

31-
export const defaultSelectedSubstances = (points: GridPoint[]): string[] =>
32-
significantSubstances(points)
33-
.sort((a, b) => maxOutput(points, b) - maxOutput(points, a))
36+
export const defaultSelectedSubstances = (simulations: SimulationResults[]): string[] =>
37+
significantSubstances(simulations)
38+
.sort((a, b) => maxOutput(simulations, b) - maxOutput(simulations, a))
3439
.slice(0, MAX_DEFAULT_SERIES);
3540

3641
export const buildGridCsv = (result: GridSimulationResult): string => {
37-
const rows: TabulatedResultRow[] = result.points
38-
.filter((point) => point.status === "done")
39-
.map((point) => {
42+
const rows: TabulatedResultRow[] = result.simulations
43+
.filter((sim) => sim.status === "done")
44+
.map((sim) => {
4045
const row: TabulatedResultRow = {};
41-
result.axes.forEach((axis, i) => {
42-
row[`In_${axis.substance}`] = point.coordinates[i];
46+
result.axes.forEach((axis) => {
47+
row[`In_${axis.substance}`] = sim.input.concentrations[axis.substance] ?? 0;
4348
});
44-
Object.entries(point.concentrations).forEach(([substance, value]) => {
49+
Object.entries(lastPhaseConcentrations(sim)).forEach(([substance, value]) => {
4550
row[`Out_${substance}`] = value;
4651
});
4752
return row;

0 commit comments

Comments
 (0)