Skip to content
Draft
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
9 changes: 7 additions & 2 deletions libs/@hashintel/petrinaut/src/constants/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { differentialEquationsListSubView } from "../views/Editor/subviews/diffe
import { nodesListSubView } from "../views/Editor/subviews/nodes-list";
import { parametersListSubView } from "../views/Editor/subviews/parameters-list";
import { simulationSettingsSubView } from "../views/Editor/subviews/simulation-settings";
import { simulationTimelineSubView } from "../views/Editor/subviews/simulation-timeline";
import { typesListSubView } from "../views/Editor/subviews/types-list";

// Panel margin (spacing around panels)
export const PANEL_MARGIN = 10;
export const PANEL_MARGIN = 8;

// Resize handle
export const RESIZE_HANDLE_SIZE = 20;
export const RESIZE_HANDLE_SIZE = 16;
export const RESIZE_HANDLE_OFFSET = -Math.floor(RESIZE_HANDLE_SIZE / 2);

// Left Sidebar
Expand Down Expand Up @@ -43,7 +44,11 @@ export const LEFT_SIDEBAR_SUBVIEWS: SubView[] = [
nodesListSubView,
];

// Base subviews always visible in the bottom panel
export const BOTTOM_PANEL_SUBVIEWS: SubView[] = [
diagnosticsSubView,
simulationSettingsSubView,
];

// Subviews only visible when simulation is running/paused
export const SIMULATION_ONLY_SUBVIEWS: SubView[] = [simulationTimelineSubView];
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe("buildSimulation", () => {
},
],
]),
parameterValues: {},
seed: 42,
dt: 0.1,
};
Expand Down Expand Up @@ -196,6 +197,7 @@ describe("buildSimulation", () => {
],
// p3 has no initial tokens
]),
parameterValues: {},
seed: 123,
dt: 0.05,
};
Expand Down Expand Up @@ -305,6 +307,7 @@ describe("buildSimulation", () => {
},
],
]),
parameterValues: {},
seed: 42,
dt: 0.1,
};
Expand Down Expand Up @@ -360,6 +363,7 @@ describe("buildSimulation", () => {
},
],
]),
parameterValues: {},
seed: 42,
dt: 0.1,
};
Expand Down
66 changes: 40 additions & 26 deletions libs/@hashintel/petrinaut/src/core/simulation/build-simulation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { deriveDefaultParameterValues } from "../../hooks/use-default-parameter-values";
import {
deriveDefaultParameterValues,
mergeParameterValues,
} from "../../hooks/use-default-parameter-values";
import { SDCPNItemError } from "../errors";
import type {
DifferentialEquationFn,
Expand All @@ -17,15 +20,15 @@ import { compileUserCode } from "./compile-user-code";
*/
function getPlaceDimensions(
place: SimulationInput["sdcpn"]["places"][0],
sdcpn: SimulationInput["sdcpn"],
sdcpn: SimulationInput["sdcpn"]
): number {
if (!place.colorId) {
return 0;
}
const type = sdcpn.types.find((tp) => tp.id === place.colorId);
if (!type) {
throw new Error(
`Type with ID ${place.colorId} referenced by place ${place.id} does not exist in SDCPN`,
`Type with ID ${place.colorId} referenced by place ${place.id} does not exist in SDCPN`
);
}
return type.elements.length;
Expand All @@ -52,23 +55,34 @@ function getPlaceDimensions(
* @throws {Error} if user code fails to compile
*/
export function buildSimulation(input: SimulationInput): SimulationInstance {
const { sdcpn, initialMarking, seed, dt } = input;
const {
sdcpn,
initialMarking,
parameterValues: inputParameterValues,
seed,
dt,
} = input;

// Build maps for quick lookup
const placesMap = new Map(sdcpn.places.map((place) => [place.id, place]));
const transitionsMap = new Map(
sdcpn.transitions.map((transition) => [transition.id, transition]),
sdcpn.transitions.map((transition) => [transition.id, transition])
);
const typesMap = new Map(sdcpn.types.map((type) => [type.id, type]));

// Build parameter values from SDCPN parameters using the shared utility
const parameterValues = deriveDefaultParameterValues(sdcpn.parameters);
// Build parameter values: merge input values with SDCPN defaults
// Input values (from simulation store) take precedence over defaults
const defaultParameterValues = deriveDefaultParameterValues(sdcpn.parameters);
const parameterValues = mergeParameterValues(
inputParameterValues,
defaultParameterValues
);

// Validate that all places in initialMarking exist in SDCPN
for (const placeId of initialMarking.keys()) {
if (!placesMap.has(placeId)) {
throw new Error(
`Place with ID ${placeId} in initialMarking does not exist in SDCPN`,
`Place with ID ${placeId} in initialMarking does not exist in SDCPN`
);
}
}
Expand All @@ -80,7 +94,7 @@ export function buildSimulation(input: SimulationInput): SimulationInstance {
const expectedSize = dimensions * marking.count;
if (marking.values.length !== expectedSize) {
throw new Error(
`Token dimension mismatch for place ${placeId}. Expected ${expectedSize} values (${dimensions} dimensions Γ— ${marking.count} tokens), got ${marking.values.length}`,
`Token dimension mismatch for place ${placeId}. Expected ${expectedSize} values (${dimensions} dimensions Γ— ${marking.count} tokens), got ${marking.values.length}`
);
}
}
Expand All @@ -94,27 +108,27 @@ export function buildSimulation(input: SimulationInput): SimulationInstance {
}

const differentialEquation = sdcpn.differentialEquations.find(
(de) => de.id === place.differentialEquationId,
(de) => de.id === place.differentialEquationId
);
if (!differentialEquation) {
throw new Error(
`Differential equation with ID ${place.differentialEquationId} referenced by place ${place.id} does not exist in SDCPN`,
`Differential equation with ID ${place.differentialEquationId} referenced by place ${place.id} does not exist in SDCPN`
);
}
const { code } = differentialEquation;

try {
const fn = compileUserCode<[Record<string, number>[], ParameterValues]>(
code,
"Dynamics",
"Dynamics"
);
differentialEquationFns.set(place.id, fn as DifferentialEquationFn);
} catch (error) {
throw new SDCPNItemError(
`Failed to compile differential equation for place \`${place.name}\`:\n\n${
error instanceof Error ? error.message : String(error)
}`,
place.id,
`Failed to compile differential equation for place \`${
place.name
}\`:\n\n${error instanceof Error ? error.message : String(error)}`,
place.id
);
}
}
Expand All @@ -129,10 +143,10 @@ export function buildSimulation(input: SimulationInput): SimulationInstance {
lambdaFns.set(transition.id, fn as LambdaFn);
} catch (error) {
throw new SDCPNItemError(
`Failed to compile Lambda function for transition \`${transition.name}\`:\n\n${
error instanceof Error ? error.message : String(error)
}`,
transition.id,
`Failed to compile Lambda function for transition \`${
transition.name
}\`:\n\n${error instanceof Error ? error.message : String(error)}`,
transition.id
);
}
}
Expand All @@ -152,7 +166,7 @@ export function buildSimulation(input: SimulationInput): SimulationInstance {
// without typed output places (they don't need to generate token data)
transitionKernelFns.set(
transition.id,
(() => ({})) as TransitionKernelFn,
(() => ({})) as TransitionKernelFn
);
continue;
}
Expand All @@ -164,10 +178,10 @@ export function buildSimulation(input: SimulationInput): SimulationInstance {
transitionKernelFns.set(transition.id, fn as TransitionKernelFn);
} catch (error) {
throw new SDCPNItemError(
`Failed to compile transition kernel for transition \`${transition.name}\`:\n\n${
error instanceof Error ? error.message : String(error)
}`,
transition.id,
`Failed to compile transition kernel for transition \`${
transition.name
}\`:\n\n${error instanceof Error ? error.message : String(error)}`,
transition.id
);
}
}
Expand Down Expand Up @@ -224,7 +238,7 @@ export function buildSimulation(input: SimulationInput): SimulationInstance {
instance: transition,
timeSinceLastFiring: 0,
},
]),
])
);

// Create the simulation instance (without frames initially)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe("computeNextFrame", () => {
const simulation = buildSimulation({
sdcpn,
initialMarking,
parameterValues: {},
seed: 42,
dt: 0.1,
});
Expand Down Expand Up @@ -114,6 +115,7 @@ describe("computeNextFrame", () => {
const simulation = buildSimulation({
sdcpn,
initialMarking,
parameterValues: {},
seed: 42,
dt: 0.1,
});
Expand Down Expand Up @@ -167,6 +169,7 @@ describe("computeNextFrame", () => {
const simulation = buildSimulation({
sdcpn,
initialMarking,
parameterValues: {},
seed: 42,
dt: 0.1,
});
Expand Down
8 changes: 5 additions & 3 deletions libs/@hashintel/petrinaut/src/core/types/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ export type ParameterValues = Record<string, number | boolean>;

export type DifferentialEquationFn = (
tokens: Record<string, number>[],
parameters: ParameterValues,
parameters: ParameterValues
) => Record<string, number>[];

export type LambdaFn = (
tokenValues: Record<string, Record<string, number>[]>,
parameters: ParameterValues,
parameters: ParameterValues
) => number | boolean;

export type TransitionKernelFn = (
tokenValues: Record<string, Record<string, number>[]>,
parameters: ParameterValues,
parameters: ParameterValues
) => Record<string, Record<string, number>[]>;

export type SimulationInput = {
sdcpn: SDCPN;
initialMarking: Map<string, { values: Float64Array; count: number }>;
/** Parameter values from the simulation store (overrides SDCPN defaults) */
parameterValues: Record<string, string>;
seed: number;
dt: number;
};
Expand Down
21 changes: 18 additions & 3 deletions libs/@hashintel/petrinaut/src/examples/broken-machines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,13 +361,13 @@ export const productionMachines: { title: string; petriNetDefinition: SDCPN } =
id: "5bfea547-faaf-4626-8662-6400d07c049e",
name: "Reparation Dynamics",
colorId: "type__1762560152725",
code: '// This function defines the differential equation for the place of type "Machine".\n// The function receives the current tokens in all places and the parameters.\n// It should return the derivative of the token value in this place.\nexport default Dynamics((tokens, parameters) => {\n return tokens.map(({ machine_damage_ratio }) => {\n // ...Do some computation with input token here if needed\n\n return {\n machine_damage_ratio: -1 / 3\n };\n });\n});',
code: '// This function defines the differential equation for the place of type "Machine".\n// The function receives the current tokens in all places and the parameters.\n// It should return the derivative of the token value in this place.\nexport default Dynamics((tokens, parameters) => {\n return tokens.map(({ machine_damage_ratio }) => {\n // ...Do some computation with input token here if needed\n\n return {\n machine_damage_ratio: -parameters.damage_reparation_per_second\n };\n });\n});',
},
{
id: "ca26e5e2-0373-46a9-920e-a6eacadd92e8",
name: "Production Dynamics",
colorId: "type__1762560154179",
code: '// This function defines the differential equation for the place of type "Machine Producing Product".\n// The function receives the current tokens in all places and the parameters.\n// It should return the derivative of the token value in this place.\nexport default Dynamics((tokens, parameters) => {\n return tokens.map(({ machine_damage_ratio, transformation_progress }) => {\n // ...Do some computation with input token here if needed\n\n return {\n machine_damage_ratio: 1 / 1000,\n transformation_progress: 1 / 3\n };\n });\n});',
code: '// This function defines the differential equation for the place of type "Machine Producing Product".\n// The function receives the current tokens in all places and the parameters.\n// It should return the derivative of the token value in this place.\nexport default Dynamics((tokens, parameters) => {\n return tokens.map(({ machine_damage_ratio, transformation_progress }) => {\n // ...Do some computation with input token here if needed\n\n return {\n machine_damage_ratio: parameters.damage_per_second,\n transformation_progress: 1 / 3\n };\n });\n});',
},
{
id: "887245c3-183c-4dac-a1aa-d602d21b6450",
Expand All @@ -376,6 +376,21 @@ export const productionMachines: { title: string; petriNetDefinition: SDCPN } =
code: '// This function defines the differential equation for the place of type "Technician".\n// The function receives the current tokens in all places and the parameters.\n// It should return the derivative of the token value in this place.\nexport default Dynamics((tokens, parameters) => {\n return tokens.map(({ distance_to_site }) => {\n // ...Do some computation with input token here if needed\n\n return {\n distance_to_site: -1\n };\n });\n});',
},
],
parameters: [],
parameters: [
{
id: "param__damage_per_second",
name: "Damage Per Second",
variableName: "damage_per_second",
type: "real",
defaultValue: "0.001",
},
{
id: "param__damage_reparation_per_second",
name: "Damage Reparation Per Second",
variableName: "damage_reparation_per_second",
type: "real",
defaultValue: "0.333",
},
],
},
};
43 changes: 30 additions & 13 deletions libs/@hashintel/petrinaut/src/examples/sir-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export const sirModel: { title: string; petriNetDefinition: SDCPN } = {
colorId: null,
dynamicsEnabled: false,
differentialEquationId: null,
x: -300,
y: 0,
x: -375,
y: 135,
width: 130,
height: 130,
},
Expand All @@ -21,8 +21,8 @@ export const sirModel: { title: string; petriNetDefinition: SDCPN } = {
colorId: null,
dynamicsEnabled: false,
differentialEquationId: null,
x: 0,
y: 0,
x: -195,
y: 285,
width: 130,
height: 130,
},
Expand All @@ -32,8 +32,8 @@ export const sirModel: { title: string; petriNetDefinition: SDCPN } = {
colorId: null,
dynamicsEnabled: false,
differentialEquationId: null,
x: 300,
y: 0,
x: 315,
y: 120,
width: 130,
height: 130,
},
Expand All @@ -59,11 +59,12 @@ export const sirModel: { title: string; petriNetDefinition: SDCPN } = {
},
],
lambdaType: "stochastic",
lambdaCode: "export default Lambda(() => 0.3)",
lambdaCode:
"export default Lambda((tokens, parameters) => parameters.infection_rate)",
transitionKernelCode:
"export default TransitionKernel(() => {\n return {\n Infected: [{}, {}],\n };\n});",
x: -150,
y: 0,
x: -165,
y: 75,
width: 160,
height: 80,
},
Expand All @@ -83,17 +84,33 @@ export const sirModel: { title: string; petriNetDefinition: SDCPN } = {
},
],
lambdaType: "stochastic",
lambdaCode: "export default Lambda(() => 0.1)",
lambdaCode:
"export default Lambda((tokens, parameters) => parameters.recovery_rate)",
transitionKernelCode:
"export default TransitionKernel(() => {\n return {\n Recovered: [{}],\n };\n});",
x: 150,
y: 0,
x: 75,
y: 225,
width: 160,
height: 80,
},
],
types: [],
differentialEquations: [],
parameters: [],
parameters: [
{
id: "param__infection_rate",
name: "Infection Rate",
variableName: "infection_rate",
type: "real",
defaultValue: "3",
},
{
id: "param__recovery_rate",
name: "Recovery Rate",
variableName: "recovery_rate",
type: "real",
defaultValue: "1",
},
],
},
};
Loading
Loading