Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions e2e/units.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect, test } from "@playwright/test";
import { createBlankProject, gotoApp, waitForEngine } from "./helpers";

for (const [units, time, pressure] of [
["real", "fs", "atm"],
["metal", "ps", "bar"],
]) {
test(`real engine chart labels use ${units} units from an included file`, async ({
page,
}) => {
await gotoApp(page);
await createBlankProject(page, "Chart units", "chart-units");
await page.getByTestId("upload-input").setInputFiles([
{
name: "settings.inc",
mimeType: "text/plain",
buffer: Buffer.from(`units ${units}\n`),
},
{
name: "in.units",
mimeType: "text/plain",
buffer: Buffer.from(
"include settings.inc\natom_style atomic\nregion box block 0 4 0 4 0 4\ncreate_box 1 box\ncreate_atoms 1 single 1 1 1\nmass * 1\npair_style zero 1\npair_coeff * *\nrun 0\n",
),
},
]);
await waitForEngine(page);
await page.getByTestId("run-file-in.units").click();
await expect
.poll(
() =>
page.evaluate(async () => {
const path = "/atomify/src/store/index.ts";
const s = (await import(path)).default.getState().simulationStatus;
return {
units: s.unitStyle,
x: s.computes.thermo_press?.xLabel,
y: s.computes.thermo_press?.yLabel,
};
}),
{ timeout: 60_000 },
)
.toEqual({ units, x: `Time (${time})`, y: `Pressure (${pressure})` });
});
}
17 changes: 11 additions & 6 deletions src/components/SelectedAtomsInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UNIT_SYSTEMS } from "../utils/units";
import { Button } from "antd";
import { Particles } from "omovi";
import { useMemo, useState, useEffect, useRef } from "react";
Expand Down Expand Up @@ -144,6 +145,10 @@ const SelectedAtomsInfo = ({
const prevRunningRef = useRef<boolean>(false);

// Get running state from store
const unitStyle = useStoreState((state) => state.simulationStatus.unitStyle);
const lengthUnit = unitStyle
? UNIT_SYSTEMS[unitStyle].length
: "simulation units";
const running = useStoreState((state) => state.simulation.running);

// Clear time-series data when simulation starts (running goes from false to true)
Expand Down Expand Up @@ -414,7 +419,7 @@ const SelectedAtomsInfo = ({
<MeasurementRow
label="Distance"
value={distance.toFixed(3)}
unit="Å"
unit={lengthUnit}
plotKey={distanceKey}
timeSeriesData={timeSeriesData}
onPlotClick={setVisiblePlot}
Expand All @@ -433,7 +438,7 @@ const SelectedAtomsInfo = ({
atomData[0].position,
atomData[1].position,
).toFixed(3),
unit: "Å",
unit: lengthUnit,
plotKey: getCanonicalDistanceKey(
atomData[0].atomId,
atomData[1].atomId,
Expand All @@ -445,7 +450,7 @@ const SelectedAtomsInfo = ({
atomData[1].position,
atomData[2].position,
).toFixed(3),
unit: "Å",
unit: lengthUnit,
plotKey: getCanonicalDistanceKey(
atomData[1].atomId,
atomData[2].atomId,
Expand All @@ -457,7 +462,7 @@ const SelectedAtomsInfo = ({
atomData[0].position,
atomData[2].position,
).toFixed(3),
unit: "Å",
unit: lengthUnit,
plotKey: getCanonicalDistanceKey(
atomData[0].atomId,
atomData[2].atomId,
Expand Down Expand Up @@ -596,9 +601,9 @@ const SelectedAtomsInfo = ({
<Figure
plotData={{
data1D: timeSeriesData[visiblePlot],
xLabel: "Time",
xLabel: "Timestep",
yLabel: visiblePlot.startsWith("distance")
? "Distance (Å)"
? `Distance (${lengthUnit})`
: "Angle (°)",
name: getPlotName(visiblePlot),
}}
Expand Down
10 changes: 9 additions & 1 deletion src/store/processing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function fixture() {
const lammps = {
getCellMatrixPointer: () => 0,
getOrigoPointer: () => 48,
getUnitStyle: vi.fn(() => "metal"),
getDimension: () => 3,
getTimesteps: () => 42,
getNumAtoms: () => 256,
Expand Down Expand Up @@ -53,13 +54,20 @@ describe("post-timestep status publication", () => {
numAtoms: status.numAtoms,
memoryUsage: status.memoryUsage,
runType: status.runType,
unitStyle: status.unitStyle,
});
previous = status;
}
});
await store.getActions().processing.runPostTimestep(false);
expect(observed).toEqual([
{ timesteps: 42, numAtoms: 256, memoryUsage: 8192, runType: "Dynamics" },
{
timesteps: 42,
numAtoms: 256,
memoryUsage: 8192,
runType: "Dynamics",
unitStyle: "metal",
},
]);
expect(store.getState().simulationStatus).toMatchObject({
runTimesteps: 40,
Expand Down
1 change: 1 addition & 0 deletions src/store/processing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export const processingModel: ProcessingModel = {
box: getSimulationBox(lammps, wasm, currentStatus.box),
origo: getSimulationOrigo(lammps, wasm, currentStatus.origo),
dimension: lammps.getDimension(),
unitStyle: lammps.getUnitStyle?.(),
timesteps: lammps.getTimesteps(),
numAtoms: lammps.getNumAtoms(),
runTimesteps: lammps.getRunTimesteps(),
Expand Down
2 changes: 2 additions & 0 deletions src/store/simulationstatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface SimulationStatusData {
computes: { [key: string]: Compute };
fixes: { [key: string]: Fix };
variables: { [key: string]: Variable };
unitStyle?: import("../utils/units").UnitStyle;
dimension: number;
walls: Wall[];
}
Expand Down Expand Up @@ -169,6 +170,7 @@ export const simulationStatusModel: SimulationStatusModel = {
state.box = undefined;
state.origo = undefined;
state.dimension = 3;
state.unitStyle = undefined;
state.walls = [];
}),
};
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type LammpsWeb = {

computeBonds: () => number;
computeParticles: () => number;
getUnitStyle?: () => import("./utils/units").UnitStyle | undefined;
getDimension: () => number;
getWalls: () => CPPArray<Wall>;
};
Expand Down
51 changes: 51 additions & 0 deletions src/utils/units.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, expect, it } from "vitest";
import {
modifierAxisLabels as labels,
parseUnitStyle,
UNIT_SYSTEMS,
} from "./units";

describe("LAMMPS axis units", () => {
it("recognizes runtime setup output without interpreting script text", () => {
expect(parseUnitStyle(" Unit style : metal")).toBe("metal");
expect(parseUnitStyle("units real")).toBeUndefined();
expect(parseUnitStyle("Unit style : unknown")).toBeUndefined();
});
it("distinguishes pressure and time in real, metal, and reduced units", () => {
expect(labels("compute", "pressure", "Time", "Pressure", "real")).toEqual({
xLabel: "Time (fs)",
yLabel: "Pressure (atm)",
});
expect(labels("compute", "pressure", "Time", "Pressure", "metal")).toEqual({
xLabel: "Time (ps)",
yLabel: "Pressure (bar)",
});
expect(labels("compute", "temp", "Time", "Value", "lj").yLabel).toBe(
"Temperature (reduced LJ)",
);
});
it("uses distance for RDF and squared dimensions for correlation plots", () => {
expect(labels("compute", "rdf", "r", "RDF", "si")).toEqual({
xLabel: "r (m)",
yLabel: "RDF (dimensionless)",
});
expect(labels("compute", "msd", "Time", "MSD", "nano").yLabel).toBe(
"Mean square displacement (nm²)",
);
expect(labels("compute", "vacf", "Time", "VACF", "electron").yLabel).toBe(
"VACF ((Bohr/atomic time unit)²)",
);
});
it("does not invent dimensions for user expressions or unavailable metadata", () => {
expect(
labels("variable", "equal", "Time", "Value", "real").yLabel,
).toContain("user-defined units");
expect(
labels("compute", "custom", "Time", "Value", "real").yLabel,
).toContain("compute-defined units");
expect(labels("compute", "temp", "Time", "Value").yLabel).toBe(
"Temperature (simulation units)",
);
expect(Object.keys(UNIT_SYSTEMS)).toHaveLength(8);
});
});
112 changes: 112 additions & 0 deletions src/utils/units.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/** Native LAMMPS output units: https://docs.lammps.org/units.html */
export const UNIT_SYSTEMS = {
lj: {
time: "τ",
length: "σ",
energy: "ε",
temperature: "reduced LJ",
pressure: "ε/σ³",
velocity: "σ/τ",
},
real: {
time: "fs",
length: "Å",
energy: "kcal/mol",
temperature: "K",
pressure: "atm",
velocity: "Å/fs",
},
metal: {
time: "ps",
length: "Å",
energy: "eV",
temperature: "K",
pressure: "bar",
velocity: "Å/ps",
},
si: {
time: "s",
length: "m",
energy: "J",
temperature: "K",
pressure: "Pa",
velocity: "m/s",
},
cgs: {
time: "s",
length: "cm",
energy: "erg",
temperature: "K",
pressure: "dyn/cm²",
velocity: "cm/s",
},
electron: {
time: "fs",
length: "Bohr",
energy: "Hartree",
temperature: "K",
pressure: "Pa",
velocity: "Bohr/atomic time unit",
},
micro: {
time: "µs",
length: "µm",
energy: "pg·µm²/µs²",
temperature: "K",
pressure: "pg/(µm·µs²)",
velocity: "µm/µs",
},
nano: {
time: "ns",
length: "nm",
energy: "ag·nm²/ns²",
temperature: "K",
pressure: "ag/(nm·ns²)",
velocity: "nm/ns",
},
} as const;
export type UnitStyle = keyof typeof UNIT_SYSTEMS;

export function parseUnitStyle(line: string): UnitStyle | undefined {
const match = /^\s*Unit style\s*:\s*(\w+)\s*$/.exec(line);
return match && Object.hasOwn(UNIT_SYSTEMS, match[1])
? (match[1] as UnitStyle)
: undefined;
}

export function modifierAxisLabels(
category: string,
style: string,
xLabel: string,
yLabel: string,
units?: UnitStyle,
) {
const u = units && UNIT_SYSTEMS[units];
const label = (name: string, unit?: string) =>
`${name} (${unit ?? "simulation units"})`;
const x =
xLabel === "Time"
? label("Time", u?.time)
: category === "compute" && style === "rdf"
? label(xLabel, u?.length)
: xLabel;
if (category !== "compute")
return { xLabel: x, yLabel: label(yLabel, "user-defined units") };
const known: Record<string, [string, string | undefined]> = {
temp: ["Temperature", u?.temperature],
pressure: ["Pressure", u?.pressure],
pe: ["Potential energy", u?.energy],
ke: ["Kinetic energy", u?.energy],
msd: ["Mean square displacement", u && `${u.length}²`],
vacf: ["VACF", u && `(${u.velocity})²`],
rdf: [yLabel, "dimensionless"],
gyration: ["Radius of gyration", u?.length],
};
const dimension = known[style.replace(/\/kk$/, "")];
return {
xLabel: x,
yLabel: dimension
? label(...dimension)
: label(yLabel, "compute-defined units"),
};
}
5 changes: 5 additions & 0 deletions src/wasm/LammpsWorkerProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export class LammpsWorkerProxy implements LammpsWeb {
private cBondCount = 0;
private cStep = 0;
private cDimension = 3;
private cUnitStyle?: import("../utils/units").UnitStyle;
private cRunMode = 0;
private cRunStepsDone = 0;
private cRunStepsTotal = 0;
Expand Down Expand Up @@ -282,6 +283,7 @@ export class LammpsWorkerProxy implements LammpsWeb {
this.cBondCount = step.bondCount;
this.cStep = step.step;
this.cDimension = step.dimension;
this.cUnitStyle = step.unitStyle;
this.cRunMode = step.runMode;
this.cRunStepsDone = step.runStepsDone;
this.cRunStepsTotal = step.runStepsTotal;
Expand Down Expand Up @@ -610,6 +612,9 @@ export class LammpsWorkerProxy implements LammpsWeb {
getOrigoPointer() {
return this.origPtr;
}
getUnitStyle() {
return this.cUnitStyle;
}
getDimension() {
return this.cDimension;
}
Expand Down
8 changes: 6 additions & 2 deletions src/wasm/lammps.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ function streamStep() {
boxMatrix: boxMatrix.buffer,
origin: origin.buffer,
dimension: box.dimension,
unitStyle: adapter?.getUnitStyle(),
runMode: native.getRunMode(),
runStepsDone: native.getRunStepsDone(),
runStepsTotal: native.getRunStepsTotal(),
Expand All @@ -243,8 +244,11 @@ async function load() {
post({ type: "ready" });
return;
}
const printLine = (...args: unknown[]) =>
post({ type: "printed", text: args.join(" ") });
const printLine = (...args: unknown[]) => {
const text = args.join(" ");
adapter?.observeOutput(text);
post({ type: "printed", text });
};

// Fetch the atomify emscripten glue (embedded wasm, ~50 MB) once and keep it
// as a Blob. Load createModule from that same blob URL (?url + @vite-ignore
Expand Down
Loading
Loading