diff --git a/.changeset/ad-hoc-value-errors.md b/.changeset/ad-hoc-value-errors.md new file mode 100644 index 00000000000..589e2532f8e --- /dev/null +++ b/.changeset/ad-hoc-value-errors.md @@ -0,0 +1,5 @@ +--- +"@hashintel/petrinaut": patch +--- + +Improve scenario and experiment forms with keyboard navigation, stacked section headers, overlay scrollbars, default starting-place filtering with a compact toggle beside the section title, and source expressions over selected computed values. Align the Time Step label and info icon, align token totals with place headers, keep select chevrons inside spreadsheet cells, and keep validation errors visible while editing ad-hoc values. diff --git a/.changeset/clear-code-editor-guide.md b/.changeset/clear-code-editor-guide.md new file mode 100644 index 00000000000..420edfdbafc --- /dev/null +++ b/.changeset/clear-code-editor-guide.md @@ -0,0 +1,5 @@ +--- +"@hashintel/petrinaut-core": patch +--- + +Make the code editor guide available to the in-app assistant. diff --git a/.changeset/editable-example-scenarios.md b/.changeset/editable-example-scenarios.md new file mode 100644 index 00000000000..cdbb73e7189 --- /dev/null +++ b/.changeset/editable-example-scenarios.md @@ -0,0 +1,5 @@ +--- +"@hashintel/petrinaut-core": patch +--- + +Make bundled example scenarios editable in the ad-hoc scenario form while preserving their parameters and initial states. diff --git a/.changeset/experiment-interval-feedback.md b/.changeset/experiment-interval-feedback.md new file mode 100644 index 00000000000..57609b1a5dd --- /dev/null +++ b/.changeset/experiment-interval-feedback.md @@ -0,0 +1,5 @@ +--- +"@hashintel/petrinaut": patch +--- + +Offer GPU compute, parameter sweeps, and in-browser optimization without feature flags, with automatic metric names, grouped constraints that accept state expressions, keyboard controls, and explicit optimizer startup. Simplify results with manual optimizer controls, compact details, single-value parameter sliders, and sensitivity estimates ordered by importance. diff --git a/.changeset/experiment-settings-guide.md b/.changeset/experiment-settings-guide.md new file mode 100644 index 00000000000..c52283e133c --- /dev/null +++ b/.changeset/experiment-settings-guide.md @@ -0,0 +1,5 @@ +--- +"@hashintel/petrinaut-core": patch +--- + +Accept state constraint expressions without an explicit return while preserving existing code bodies. Update the assistant's guide summaries for experiment controls and settings available without simulation feature flags. diff --git a/.changeset/property-checkbox-spacing.md b/.changeset/property-checkbox-spacing.md new file mode 100644 index 00000000000..29c5330532b --- /dev/null +++ b/.changeset/property-checkbox-spacing.md @@ -0,0 +1,5 @@ +--- +"@hashintel/petrinaut": patch +--- + +Polish properties panels with readable, clickable checkbox labels, animated capacity options, a single equation link, consistent fields, and clearer section controls and dividers. diff --git a/.changeset/routed-simulation-panels.md b/.changeset/routed-simulation-panels.md new file mode 100644 index 00000000000..93741945071 --- /dev/null +++ b/.changeset/routed-simulation-panels.md @@ -0,0 +1,5 @@ +--- +"@hashintel/petrinaut": patch +--- + +Add resizable, routed Experiment and Scenario panels with keyboard selection and fullscreen views that keep the tabs and AI assistant accessible. diff --git a/.changeset/simulation-panel-guidance.md b/.changeset/simulation-panel-guidance.md new file mode 100644 index 00000000000..a98c8f4cb9b --- /dev/null +++ b/.changeset/simulation-panel-guidance.md @@ -0,0 +1,5 @@ +--- +"@hashintel/petrinaut-core": patch +--- + +Add simulation panel guidance to the AI assistant's documentation catalog. diff --git a/.changeset/tidy-code-editor-layouts.md b/.changeset/tidy-code-editor-layouts.md new file mode 100644 index 00000000000..0eb753c0388 --- /dev/null +++ b/.changeset/tidy-code-editor-layouts.md @@ -0,0 +1,5 @@ +--- +"@hashintel/petrinaut": patch +--- + +Expand code sections to fill the Properties Panel with smooth transitions, a parent breadcrumb, and browser history navigation. Make Canvas and Definitions available as Edit views with browser history navigation. diff --git a/apps/petrinaut-website/README.md b/apps/petrinaut-website/README.md index dfb954c6573..e9214dc3663 100644 --- a/apps/petrinaut-website/README.md +++ b/apps/petrinaut-website/README.md @@ -136,9 +136,7 @@ use provider-pattern discovery instead. The demo at [http://localhost:5173](http://localhost:5173) runs the optimizer in the browser: the Optuna study runs in a Pyodide web worker and each optimization step runs on Petrinaut's own experiments backend, so no Python -service is involved. With the experimental **Parameter sweeps** and -**In-browser optimization** settings on, under **Viewport controls > Settings > -Simulation**, a sweep's Parameters card in the Experiments tab offers +service is involved. A sweep's Parameters card in the Experiments tab offers **Optimize**. The first optimization in a browser downloads the Python runtime from jsDelivr and Optuna from PyPI; later runs use the browser cache. diff --git a/apps/petrinaut-website/scripts/generate-example-artifacts.ts b/apps/petrinaut-website/scripts/generate-example-artifacts.ts index d0674b8ced6..22d7b50d0b4 100644 --- a/apps/petrinaut-website/scripts/generate-example-artifacts.ts +++ b/apps/petrinaut-website/scripts/generate-example-artifacts.ts @@ -65,9 +65,12 @@ const generateRuntime = async (entry: ExampleCatalogEntry) => { null, ) as Record; for (const scenario of definition.scenarios ?? []) { - const hir = lowerScenarioToHir({ - parameterOverrides: scenario.parameterOverrides, - initialState: scenario.initialState, + const hir = lowerScenarioToHir(scenario, { + adHocContext: { + places: definition.places, + types: definition.types, + netParameters: definition.parameters, + }, }); const outcome = compileScenario( scenario, diff --git a/apps/petrinaut-website/src/examples/catalog.test.ts b/apps/petrinaut-website/src/examples/catalog.test.ts index 32828584771..5097d5e926e 100644 --- a/apps/petrinaut-website/src/examples/catalog.test.ts +++ b/apps/petrinaut-website/src/examples/catalog.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from "vitest"; +import { compileScenario } from "@hashintel/petrinaut-core"; + import { exampleCatalog, exampleSlugs, @@ -34,7 +36,16 @@ describe("example catalog", () => { expect(example.catalog).toBe(entry); for (const scenario of example.definition.scenarios ?? []) { + expect(scenario.initialState.type).toBe("adhoc"); expect(runtime.scenarioHirById).toHaveProperty(scenario.id); + const compiled = compileScenario( + scenario, + runtime.scenarioHirById[scenario.id]!, + example.definition.parameters, + example.definition.places, + example.definition.types, + ); + expect(compiled.ok, JSON.stringify(compiled)).toBe(true); for (const parameter of scenario.scenarioParameters) { const bounds = entry.parameterBounds[parameter.identifier]; diff --git a/apps/petrinaut-website/src/examples/example-search.test.ts b/apps/petrinaut-website/src/examples/example-search.test.ts index 6d53a5045ca..1b1b0acc109 100644 --- a/apps/petrinaut-website/src/examples/example-search.test.ts +++ b/apps/petrinaut-website/src/examples/example-search.test.ts @@ -8,6 +8,67 @@ import { } from "./example-search"; describe("example search contract", () => { + it("carries expanded sections as complete panel and section pairs", () => { + const expanded = { + expandedPanel: "transition-properties", + expandedSection: "transition-results", + }; + expect(validateSharedExampleSearch(expanded)).toMatchObject(expanded); + expect(canonicalSearchString(expanded)).toBe( + "expandedPanel=transition-properties&expandedSection=transition-results", + ); + for (const input of [ + { expandedPanel: "transition-properties" }, + { expandedSection: "transition-results" }, + { expandedPanel: 42, expandedSection: "transition-results" }, + { expandedPanel: "transition-properties", expandedSection: "" }, + ]) { + const search = validateSharedExampleSearch(input); + expect(search.expandedPanel).toBeUndefined(); + expect(search.expandedSection).toBeUndefined(); + } + }); + + it("validates complete resource links and fullscreen creation links", () => { + const resource = { + resourceType: "scenario", + resourceId: "scenario / one", + presentation: "fullscreen", + }; + const validated = validateSharedExampleSearch(resource); + expect(validated).toMatchObject(resource); + expect(canonicalSearchString(validated)).toBe( + "presentation=fullscreen&resourceId=scenario+%2F+one&resourceType=scenario", + ); + expect( + validateSharedExampleSearch({ + overlay: "create-experiment", + presentation: "fullscreen", + }).presentation, + ).toBe("fullscreen"); + for (const invalid of [ + { resourceType: "unknown", resourceId: "one" }, + { resourceType: "scenario" }, + { resourceType: "experiment", resourceId: "" }, + { resourceId: "one" }, + ]) { + const search = validateSharedExampleSearch({ + ...invalid, + presentation: "fullscreen", + }); + expect(search.resourceType).toBeUndefined(); + expect(search.resourceId).toBeUndefined(); + expect(search.presentation).toBeUndefined(); + } + expect( + validateSharedExampleSearch({ + resourceType: "metric", + resourceId: "one", + presentation: "fullscreen", + }).presentation, + ).toBeUndefined(); + }); + it("validates settings sections only for the user settings dialog", () => { expect( validateSharedExampleSearch({ @@ -21,6 +82,12 @@ describe("example search contract", () => { settings: "unknown", }).settings, ).toBeUndefined(); + expect( + validateSharedExampleSearch({ + overlay: "user-settings", + settings: "simulation", + }), + ).toMatchObject({ overlay: "user-settings", settings: undefined }); expect( validateSharedExampleSearch({ overlay: "create-experiment", diff --git a/apps/petrinaut-website/src/examples/example-search.ts b/apps/petrinaut-website/src/examples/example-search.ts index b245df20796..ea5f4286ca1 100644 --- a/apps/petrinaut-website/src/examples/example-search.ts +++ b/apps/petrinaut-website/src/examples/example-search.ts @@ -22,7 +22,9 @@ import { * `navigation-search.ts` maps each of these onto the editor's own vocabulary * with an exhaustive switch, so a rename on either side fails to compile. */ -export const sharedModes = ["edit", "simulate", "actual", "notebook"] as const; +export const sharedModes = ["edit", "simulate", "actual"] as const; + +export const sharedEditViews = ["canvas", "definitions"] as const; export const sharedSimulateViews = [ "scenarios", @@ -38,13 +40,15 @@ export const sharedOverlays = [ "create-experiment", ] as const; -export const sharedSettingsSections = [ - "general", - "viewport", - "simulation", - "labs", +export const sharedSettingsSections = ["general", "viewport", "labs"] as const; + +export const sharedResourceTypes = [ + "scenario", + "metric", + "experiment", ] as const; +export type SharedEditView = (typeof sharedEditViews)[number]; export type SharedMode = (typeof sharedModes)[number]; export type SharedSimulateView = (typeof sharedSimulateViews)[number]; export type SharedOverlay = (typeof sharedOverlays)[number]; @@ -64,9 +68,15 @@ export type SharedExampleSearch = { itemType?: SelectionItemType; itemId?: string; mode?: SharedMode; + editView?: SharedEditView; view?: SharedSimulateView; overlay?: SharedOverlay; settings?: (typeof sharedSettingsSections)[number]; + expandedPanel?: string; + expandedSection?: string; + resourceType?: (typeof sharedResourceTypes)[number]; + resourceId?: string; + presentation?: "fullscreen"; }; /** The keys this contract owns. Anything else in a URL is foreign. */ @@ -76,9 +86,15 @@ const sharedSearchKeys = [ "itemType", "itemId", "mode", + "editView", "view", "overlay", "settings", + "expandedPanel", + "expandedSection", + "resourceType", + "resourceId", + "presentation", ] as const satisfies readonly (keyof SharedExampleSearch)[]; // `.catch(undefined)` is the contract's whole validation story: anything a URL @@ -90,6 +106,7 @@ const optionalSelectionItemType = z .optional() .catch(undefined); +const optionalEditView = z.enum(sharedEditViews).optional().catch(undefined); const optionalMode = z.enum(sharedModes).optional().catch(undefined); const optionalSimulateView = z .enum(sharedSimulateViews) @@ -121,22 +138,54 @@ export const selectionToSearch = ( */ export const validateSharedExampleSearch = ( input: Record, -): SharedExampleSearch => ({ - scenario: optionalNonEmptyString.parse(input.scenario), - subnet: optionalNonEmptyString.parse(input.subnet), - mode: optionalMode.parse(input.mode), - view: optionalSimulateView.parse(input.view), - overlay: optionalOverlay.parse(input.overlay), - settings: - input.overlay === "user-settings" - ? z - .enum(sharedSettingsSections) - .optional() - .catch(undefined) - .parse(input.settings) +): SharedExampleSearch => { + const resourceType = z + .enum(sharedResourceTypes) + .optional() + .catch(undefined) + .parse(input.resourceType); + const resourceId = optionalNonEmptyString.parse(input.resourceId); + const hasResource = resourceType !== undefined && resourceId !== undefined; + const canExpand = + (hasResource && resourceType !== "metric") || + input.overlay === "create-scenario" || + input.overlay === "create-experiment"; + + return { + scenario: optionalNonEmptyString.parse(input.scenario), + subnet: optionalNonEmptyString.parse(input.subnet), + mode: input.mode === "notebook" ? "edit" : optionalMode.parse(input.mode), + editView: + input.mode === "notebook" + ? "definitions" + : optionalEditView.parse( + input.editView === "notebook" ? "definitions" : input.editView, + ), + view: optionalSimulateView.parse(input.view), + overlay: optionalOverlay.parse(input.overlay), + expandedPanel: optionalNonEmptyString.parse(input.expandedSection) + ? optionalNonEmptyString.parse(input.expandedPanel) : undefined, - ...selectionToSearch(selectionFromInput(input)), -}); + expandedSection: optionalNonEmptyString.parse(input.expandedPanel) + ? optionalNonEmptyString.parse(input.expandedSection) + : undefined, + settings: + input.overlay === "user-settings" + ? z + .enum(sharedSettingsSections) + .optional() + .catch(undefined) + .parse(input.settings) + : undefined, + ...selectionToSearch(selectionFromInput(input)), + resourceType: hasResource ? resourceType : undefined, + resourceId: hasResource ? resourceId : undefined, + presentation: + canExpand && input.presentation === "fullscreen" + ? "fullscreen" + : undefined, + }; +}; /** Canonical query string for a validated search: sorted, contract keys only. */ export const canonicalSearchString = (search: SharedExampleSearch): string => { diff --git a/apps/petrinaut-website/src/examples/models/gases-1-pn-consumption-trigger.json b/apps/petrinaut-website/src/examples/models/gases-1-pn-consumption-trigger.json index 938d4918e66..788e93c1a59 100644 --- a/apps/petrinaut-website/src/examples/models/gases-1-pn-consumption-trigger.json +++ b/apps/petrinaut-website/src/examples/models/gases-1-pn-consumption-trigger.json @@ -430,8 +430,125 @@ "param__draw_enabled": "scenario.draw_enabled" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: 1,\n LoadsDelivered: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: 0,\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SteadyNitrogenUnitsDrawnSinceOrder: 0,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "draw_enabled", + "type": "real", + "expression": "1", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__draw_enabled", + "expression": "scenario.draw_enabled", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s1_units_drawn_since_order": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -449,8 +566,125 @@ "param__draw_enabled": "scenario.draw_enabled" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: 1,\n LoadsDelivered: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: 0,\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SteadyNitrogenUnitsDrawnSinceOrder: 0,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "draw_enabled", + "type": "real", + "expression": "0", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__draw_enabled", + "expression": "scenario.draw_enabled", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s1_units_drawn_since_order": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } } ], diff --git a/apps/petrinaut-website/src/examples/models/gases-1-pn.json b/apps/petrinaut-website/src/examples/models/gases-1-pn.json index 24b6a80ed4b..e6e2a794557 100644 --- a/apps/petrinaut-website/src/examples/models/gases-1-pn.json +++ b/apps/petrinaut-website/src/examples/models/gases-1-pn.json @@ -415,8 +415,118 @@ "param__draw_enabled": "scenario.draw_enabled" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: 1,\n LoadsDelivered: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: 0,\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "draw_enabled", + "type": "real", + "expression": "1", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__draw_enabled", + "expression": "scenario.draw_enabled", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } }, { @@ -434,8 +544,118 @@ "param__draw_enabled": "scenario.draw_enabled" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: 1,\n LoadsDelivered: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: 0,\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "draw_enabled", + "type": "real", + "expression": "0", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__draw_enabled", + "expression": "scenario.draw_enabled", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } } ], diff --git a/apps/petrinaut-website/src/examples/models/gases-2-spn.json b/apps/petrinaut-website/src/examples/models/gases-2-spn.json index b5ab5430ce0..c149a0d0617 100644 --- a/apps/petrinaut-website/src/examples/models/gases-2-spn.json +++ b/apps/petrinaut-website/src/examples/models/gases-2-spn.json @@ -868,8 +868,202 @@ "param__draw_enabled": "scenario.draw_enabled" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: 1,\n LoadsDelivered: 0,\n Returning: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: 0,\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: 0,\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenConsumed: 0,\n SlowNitrogenEvaporated: 0,\n SlowNitrogenContents: 18,\n SlowNitrogenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "draw_enabled", + "type": "real", + "expression": "1", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__draw_enabled", + "expression": "scenario.draw_enabled", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_contents": { + "kind": "uncoloured", + "count": { + "expression": "18", + "optimize": null + } + }, + "place__s2_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } }, { @@ -887,8 +1081,202 @@ "param__draw_enabled": "scenario.draw_enabled" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: 1,\n LoadsDelivered: 0,\n Returning: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: 0,\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: 0,\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenConsumed: 0,\n SlowNitrogenEvaporated: 0,\n SlowNitrogenContents: 18,\n SlowNitrogenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "draw_enabled", + "type": "real", + "expression": "0", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__draw_enabled", + "expression": "scenario.draw_enabled", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_contents": { + "kind": "uncoloured", + "count": { + "expression": "18", + "optimize": null + } + }, + "place__s2_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } }, { @@ -906,8 +1294,202 @@ "param__draw_enabled": "scenario.draw_enabled" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: 2,\n LoadsDelivered: 0,\n Returning: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: 0,\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: 0,\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenConsumed: 0,\n SlowNitrogenEvaporated: 0,\n SlowNitrogenContents: 18,\n SlowNitrogenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "draw_enabled", + "type": "real", + "expression": "1", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__draw_enabled", + "expression": "scenario.draw_enabled", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_contents": { + "kind": "uncoloured", + "count": { + "expression": "18", + "optimize": null + } + }, + "place__s2_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } }, { @@ -931,8 +1513,214 @@ "param__route_scale": "scenario.route_scale" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: 1,\n LoadsDelivered: 0,\n Returning: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: 0,\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: 0,\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenConsumed: 0,\n SlowNitrogenEvaporated: 0,\n SlowNitrogenContents: 18,\n SlowNitrogenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "draw_enabled", + "type": "real", + "expression": "1", + "exposed": true, + "optimize": null + }, + { + "name": "route_scale", + "type": "real", + "expression": "1.5", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__draw_enabled", + "expression": "scenario.draw_enabled", + "optimize": null + }, + { + "parameterId": "param__route_scale", + "expression": "scenario.route_scale", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_contents": { + "kind": "uncoloured", + "count": { + "expression": "18", + "optimize": null + } + }, + "place__s2_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } } ], diff --git a/apps/petrinaut-website/src/examples/models/gases-3-cpn.json b/apps/petrinaut-website/src/examples/models/gases-3-cpn.json index 2f230237f8c..7b95a2aa1a8 100644 --- a/apps/petrinaut-website/src/examples/models/gases-3-cpn.json +++ b/apps/petrinaut-website/src/examples/models/gases-3-cpn.json @@ -1238,8 +1238,288 @@ "scenarioParameters": [], "parameterOverrides": {}, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: [{ product: \"nitrogen\" }, { product: \"nitrogen\" }, { product: \"oxygen\" }],\n LoadsDelivered: 0,\n Returning: [],\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: [],\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: [],\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenConsumed: 0,\n SlowNitrogenEvaporated: 0,\n SlowNitrogenContents: 18,\n SlowNitrogenUllage: 12,\n CriticalOxygenOrderPlaced: 0,\n CriticalOxygenOrderPermits: 2,\n CriticalOxygenOnRoute: [],\n CriticalOxygenVented: 0,\n CriticalOxygenLineRunning: 1,\n CriticalOxygenLineStopped: 0,\n CriticalOxygenStockouts: 0,\n CriticalOxygenConsumed: 0,\n CriticalOxygenEvaporated: 0,\n CriticalOxygenContents: 46,\n CriticalOxygenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [], + "netParameters": [], + "places": { + "place__idle_tankers": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "\"nitrogen\"", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "\"nitrogen\"", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "\"oxygen\"", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_contents": { + "kind": "uncoloured", + "count": { + "expression": "18", + "optimize": null + } + }, + "place__s2_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s3_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s3_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s3_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s3_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_contents": { + "kind": "uncoloured", + "count": { + "expression": "46", + "optimize": null + } + }, + "place__s3_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } }, { @@ -1257,8 +1537,302 @@ "param__route_scale": "scenario.route_scale" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: [{ product: \"nitrogen\" }, { product: \"nitrogen\" }, { product: \"oxygen\" }],\n LoadsDelivered: 0,\n Returning: [],\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: [],\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: [],\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenConsumed: 0,\n SlowNitrogenEvaporated: 0,\n SlowNitrogenContents: 18,\n SlowNitrogenUllage: 12,\n CriticalOxygenOrderPlaced: 0,\n CriticalOxygenOrderPermits: 2,\n CriticalOxygenOnRoute: [],\n CriticalOxygenVented: 0,\n CriticalOxygenLineRunning: 1,\n CriticalOxygenLineStopped: 0,\n CriticalOxygenStockouts: 0,\n CriticalOxygenConsumed: 0,\n CriticalOxygenEvaporated: 0,\n CriticalOxygenContents: 46,\n CriticalOxygenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "route_scale", + "type": "real", + "expression": "1.5", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__route_scale", + "expression": "scenario.route_scale", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "\"nitrogen\"", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "\"nitrogen\"", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "\"oxygen\"", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_contents": { + "kind": "uncoloured", + "count": { + "expression": "18", + "optimize": null + } + }, + "place__s2_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s3_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s3_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s3_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s3_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_contents": { + "kind": "uncoloured", + "count": { + "expression": "46", + "optimize": null + } + }, + "place__s3_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } }, { @@ -1268,8 +1842,279 @@ "scenarioParameters": [], "parameterOverrides": {}, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: [{ product: \"nitrogen\" }, { product: \"oxygen\" }],\n LoadsDelivered: 0,\n Returning: [],\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: [],\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: [],\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenConsumed: 0,\n SlowNitrogenEvaporated: 0,\n SlowNitrogenContents: 18,\n SlowNitrogenUllage: 12,\n CriticalOxygenOrderPlaced: 0,\n CriticalOxygenOrderPermits: 2,\n CriticalOxygenOnRoute: [],\n CriticalOxygenVented: 0,\n CriticalOxygenLineRunning: 1,\n CriticalOxygenLineStopped: 0,\n CriticalOxygenStockouts: 0,\n CriticalOxygenConsumed: 0,\n CriticalOxygenEvaporated: 0,\n CriticalOxygenContents: 46,\n CriticalOxygenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [], + "netParameters": [], + "places": { + "place__idle_tankers": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "\"nitrogen\"", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "\"oxygen\"", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_contents": { + "kind": "uncoloured", + "count": { + "expression": "18", + "optimize": null + } + }, + "place__s2_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s3_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s3_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s3_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s3_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_contents": { + "kind": "uncoloured", + "count": { + "expression": "46", + "optimize": null + } + }, + "place__s3_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } }, { @@ -1279,8 +2124,288 @@ "scenarioParameters": [], "parameterOverrides": {}, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: [{ product: \"nitrogen\" }, { product: \"oxygen\" }, { product: \"oxygen\" }],\n LoadsDelivered: 0,\n Returning: [],\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: [],\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenConsumed: 0,\n SteadyNitrogenEvaporated: 0,\n SteadyNitrogenContents: 42,\n SteadyNitrogenUllage: 12,\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: [],\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenConsumed: 0,\n SlowNitrogenEvaporated: 0,\n SlowNitrogenContents: 18,\n SlowNitrogenUllage: 12,\n CriticalOxygenOrderPlaced: 0,\n CriticalOxygenOrderPermits: 2,\n CriticalOxygenOnRoute: [],\n CriticalOxygenVented: 0,\n CriticalOxygenLineRunning: 1,\n CriticalOxygenLineStopped: 0,\n CriticalOxygenStockouts: 0,\n CriticalOxygenConsumed: 0,\n CriticalOxygenEvaporated: 0,\n CriticalOxygenContents: 46,\n CriticalOxygenUllage: 12,\n};" + "type": "adhoc", + "content": { + "variables": [], + "netParameters": [], + "places": { + "place__idle_tankers": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "\"nitrogen\"", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "\"oxygen\"", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "\"oxygen\"", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_contents": { + "kind": "uncoloured", + "count": { + "expression": "42", + "optimize": null + } + }, + "place__s1_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_contents": { + "kind": "uncoloured", + "count": { + "expression": "18", + "optimize": null + } + }, + "place__s2_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + }, + "place__s3_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s3_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s3_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s3_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_consumed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_evaporated": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_contents": { + "kind": "uncoloured", + "count": { + "expression": "46", + "optimize": null + } + }, + "place__s3_ullage": { + "kind": "uncoloured", + "count": { + "expression": "12", + "optimize": null + } + } + } + } } } ], diff --git a/apps/petrinaut-website/src/examples/models/gases-4-dcpn.json b/apps/petrinaut-website/src/examples/models/gases-4-dcpn.json index ce46d796856..8cb57bea868 100644 --- a/apps/petrinaut-website/src/examples/models/gases-4-dcpn.json +++ b/apps/petrinaut-website/src/examples/models/gases-4-dcpn.json @@ -1498,8 +1498,421 @@ "scenarioParameters": [], "parameterOverrides": {}, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: [{ remaining: 0, product: \"nitrogen\", payload: 12, hired: 0 }, { remaining: 0, product: \"nitrogen\", payload: 12, hired: 0 }, { remaining: 0, product: \"oxygen\", payload: 12, hired: 0 }],\n LoadsDelivered: 0,\n Returning: [],\n OpenOrders: 0,\n Hires: 0,\n Plant: [{ up: 1 }],\n Outages: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: [],\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenTank: [{ level: 42, pressure: 2, drawn: 0, boiled: 0, capacity: 54, draw: parameters.draw_1, criticality: 2, product: \"nitrogen\", spilled: 0 }],\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: [],\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenTank: [{ level: 18, pressure: 2, drawn: 0, boiled: 0, capacity: 30, draw: parameters.draw_2, criticality: 1, product: \"nitrogen\", spilled: 0 }],\n CriticalOxygenOrderPlaced: 0,\n CriticalOxygenOrderPermits: 2,\n CriticalOxygenOnRoute: [],\n CriticalOxygenVented: 0,\n CriticalOxygenLineRunning: 1,\n CriticalOxygenLineStopped: 0,\n CriticalOxygenStockouts: 0,\n CriticalOxygenTank: [{ level: 46, pressure: 2, drawn: 0, boiled: 0, capacity: 58, draw: parameters.draw_3, criticality: 3, product: \"oxygen\", spilled: 0 }],\n};" + "type": "adhoc", + "content": { + "variables": [], + "netParameters": [], + "places": { + "place__idle_tankers": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__open_orders": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__hires": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__plant": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__outages": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "42", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "54", + "optimize": null + }, + { + "expression": "parameters.draw_1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "18", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "30", + "optimize": null + }, + { + "expression": "parameters.draw_2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s3_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s3_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s3_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s3_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "46", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "58", + "optimize": null + }, + { + "expression": "parameters.draw_3", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + } + } + } } }, { @@ -1517,8 +1930,435 @@ "param__route_scale": "scenario.route_scale" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: [{ remaining: 0, product: \"nitrogen\", payload: 12, hired: 0 }, { remaining: 0, product: \"nitrogen\", payload: 12, hired: 0 }, { remaining: 0, product: \"oxygen\", payload: 12, hired: 0 }],\n LoadsDelivered: 0,\n Returning: [],\n OpenOrders: 0,\n Hires: 0,\n Plant: [{ up: 1 }],\n Outages: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: [],\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenTank: [{ level: 42, pressure: 2, drawn: 0, boiled: 0, capacity: 54, draw: parameters.draw_1, criticality: 2, product: \"nitrogen\", spilled: 0 }],\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: [],\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenTank: [{ level: 18, pressure: 2, drawn: 0, boiled: 0, capacity: 30, draw: parameters.draw_2, criticality: 1, product: \"nitrogen\", spilled: 0 }],\n CriticalOxygenOrderPlaced: 0,\n CriticalOxygenOrderPermits: 2,\n CriticalOxygenOnRoute: [],\n CriticalOxygenVented: 0,\n CriticalOxygenLineRunning: 1,\n CriticalOxygenLineStopped: 0,\n CriticalOxygenStockouts: 0,\n CriticalOxygenTank: [{ level: 46, pressure: 2, drawn: 0, boiled: 0, capacity: 58, draw: parameters.draw_3, criticality: 3, product: \"oxygen\", spilled: 0 }],\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "route_scale", + "type": "real", + "expression": "1.5", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__route_scale", + "expression": "scenario.route_scale", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__open_orders": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__hires": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__plant": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__outages": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "42", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "54", + "optimize": null + }, + { + "expression": "parameters.draw_1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "18", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "30", + "optimize": null + }, + { + "expression": "parameters.draw_2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s3_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s3_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s3_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s3_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "46", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "58", + "optimize": null + }, + { + "expression": "parameters.draw_3", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + } + } + } } }, { @@ -1528,8 +2368,400 @@ "scenarioParameters": [], "parameterOverrides": {}, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: [{ remaining: 0, product: \"nitrogen\", payload: 12, hired: 0 }, { remaining: 0, product: \"oxygen\", payload: 12, hired: 0 }],\n LoadsDelivered: 0,\n Returning: [],\n OpenOrders: 0,\n Hires: 0,\n Plant: [{ up: 1 }],\n Outages: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: [],\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenTank: [{ level: 42, pressure: 2, drawn: 0, boiled: 0, capacity: 54, draw: parameters.draw_1, criticality: 2, product: \"nitrogen\", spilled: 0 }],\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: [],\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenTank: [{ level: 18, pressure: 2, drawn: 0, boiled: 0, capacity: 30, draw: parameters.draw_2, criticality: 1, product: \"nitrogen\", spilled: 0 }],\n CriticalOxygenOrderPlaced: 0,\n CriticalOxygenOrderPermits: 2,\n CriticalOxygenOnRoute: [],\n CriticalOxygenVented: 0,\n CriticalOxygenLineRunning: 1,\n CriticalOxygenLineStopped: 0,\n CriticalOxygenStockouts: 0,\n CriticalOxygenTank: [{ level: 46, pressure: 2, drawn: 0, boiled: 0, capacity: 58, draw: parameters.draw_3, criticality: 3, product: \"oxygen\", spilled: 0 }],\n};" + "type": "adhoc", + "content": { + "variables": [], + "netParameters": [], + "places": { + "place__idle_tankers": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__open_orders": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__hires": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__plant": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__outages": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "42", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "54", + "optimize": null + }, + { + "expression": "parameters.draw_1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "18", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "30", + "optimize": null + }, + { + "expression": "parameters.draw_2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s3_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s3_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s3_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s3_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "46", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "58", + "optimize": null + }, + { + "expression": "parameters.draw_3", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + } + } + } } }, { @@ -1547,8 +2779,435 @@ "param__draw_2": "scenario.slow_draw" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: [{ remaining: 0, product: \"nitrogen\", payload: 12, hired: 0 }, { remaining: 0, product: \"nitrogen\", payload: 12, hired: 0 }, { remaining: 0, product: \"oxygen\", payload: 12, hired: 0 }],\n LoadsDelivered: 0,\n Returning: [],\n OpenOrders: 0,\n Hires: 0,\n Plant: [{ up: 1 }],\n Outages: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: [],\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenTank: [{ level: 42, pressure: 2, drawn: 0, boiled: 0, capacity: 54, draw: parameters.draw_1, criticality: 2, product: \"nitrogen\", spilled: 0 }],\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: [],\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenTank: [{ level: 18, pressure: 2, drawn: 0, boiled: 0, capacity: 30, draw: parameters.draw_2, criticality: 1, product: \"nitrogen\", spilled: 0 }],\n CriticalOxygenOrderPlaced: 0,\n CriticalOxygenOrderPermits: 2,\n CriticalOxygenOnRoute: [],\n CriticalOxygenVented: 0,\n CriticalOxygenLineRunning: 1,\n CriticalOxygenLineStopped: 0,\n CriticalOxygenStockouts: 0,\n CriticalOxygenTank: [{ level: 46, pressure: 2, drawn: 0, boiled: 0, capacity: 58, draw: parameters.draw_3, criticality: 3, product: \"oxygen\", spilled: 0 }],\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "slow_draw", + "type": "real", + "expression": "0.01", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__draw_2", + "expression": "scenario.slow_draw", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__open_orders": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__hires": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__plant": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__outages": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "42", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "54", + "optimize": null + }, + { + "expression": "parameters.draw_1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "18", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "30", + "optimize": null + }, + { + "expression": "parameters.draw_2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s3_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s3_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s3_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s3_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "46", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "58", + "optimize": null + }, + { + "expression": "parameters.draw_3", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + } + } + } } }, { @@ -1566,8 +3225,435 @@ "param__hire_enabled": "scenario.hire_enabled" }, "initialState": { - "type": "code", - "content": "return {\n IdleTankers: [{ remaining: 0, product: \"nitrogen\", payload: 12, hired: 0 }, { remaining: 0, product: \"nitrogen\", payload: 12, hired: 0 }, { remaining: 0, product: \"oxygen\", payload: 12, hired: 0 }],\n LoadsDelivered: 0,\n Returning: [],\n OpenOrders: 0,\n Hires: 0,\n Plant: [{ up: 1 }],\n Outages: 0,\n SteadyNitrogenOrderPlaced: 0,\n SteadyNitrogenOrderPermits: 2,\n SteadyNitrogenOnRoute: [],\n SteadyNitrogenVented: 0,\n SteadyNitrogenLineRunning: 1,\n SteadyNitrogenLineStopped: 0,\n SteadyNitrogenStockouts: 0,\n SteadyNitrogenTank: [{ level: 42, pressure: 2, drawn: 0, boiled: 0, capacity: 54, draw: parameters.draw_1, criticality: 2, product: \"nitrogen\", spilled: 0 }],\n SlowNitrogenOrderPlaced: 0,\n SlowNitrogenOrderPermits: 1,\n SlowNitrogenOnRoute: [],\n SlowNitrogenVented: 0,\n SlowNitrogenLineRunning: 1,\n SlowNitrogenLineStopped: 0,\n SlowNitrogenStockouts: 0,\n SlowNitrogenTank: [{ level: 18, pressure: 2, drawn: 0, boiled: 0, capacity: 30, draw: parameters.draw_2, criticality: 1, product: \"nitrogen\", spilled: 0 }],\n CriticalOxygenOrderPlaced: 0,\n CriticalOxygenOrderPermits: 2,\n CriticalOxygenOnRoute: [],\n CriticalOxygenVented: 0,\n CriticalOxygenLineRunning: 1,\n CriticalOxygenLineStopped: 0,\n CriticalOxygenStockouts: 0,\n CriticalOxygenTank: [{ level: 46, pressure: 2, drawn: 0, boiled: 0, capacity: 58, draw: parameters.draw_3, criticality: 3, product: \"oxygen\", spilled: 0 }],\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "hire_enabled", + "type": "real", + "expression": "0", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__hire_enabled", + "expression": "scenario.hire_enabled", + "optimize": null + } + ], + "places": { + "place__idle_tankers": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__loads_delivered": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__returning": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__open_orders": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__hires": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__plant": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__outages": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s1_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s1_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s1_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s1_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "42", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "54", + "optimize": null + }, + { + "expression": "parameters.draw_1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s2_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s2_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s2_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s2_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "18", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "30", + "optimize": null + }, + { + "expression": "parameters.draw_2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "\"nitrogen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__s3_order_placed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_order_permits": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__s3_on_route": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__s3_vented": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_line_running": { + "kind": "uncoloured", + "count": { + "expression": "1", + "optimize": null + } + }, + "place__s3_line_stopped": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_stockouts": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__s3_tank": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "46", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "58", + "optimize": null + }, + { + "expression": "parameters.draw_3", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "\"oxygen\"", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + } + } + } } } ], diff --git a/apps/petrinaut-website/src/examples/models/semiconductor-fab-drift.json b/apps/petrinaut-website/src/examples/models/semiconductor-fab-drift.json index d8a63a269e0..150226a7310 100644 --- a/apps/petrinaut-website/src/examples/models/semiconductor-fab-drift.json +++ b/apps/petrinaut-website/src/examples/models/semiconductor-fab-drift.json @@ -1409,8 +1409,2737 @@ "param__maintenance_threshold": "scenario.maintenance_threshold" }, "initialState": { - "type": "code", - "content": "return {\n FabEntrance: [],\n WIPQueue: [\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 21, priority: 1.0, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 21, priority: 1.0, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 }\n ],\n BatchQueue: [],\n InProcess: [],\n PostProcess: [],\n InInspection: [],\n Finished: [],\n Scrapped: [],\n WIPPermits: Math.max(0, parameters.wip_cap - 35),\n ChambersAvailable: [\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 1, chamber_idx: 0, qualification: 1, process_drift: 0.0, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 1, chamber_idx: 1, qualification: 2, process_drift: 0.005, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.015, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 1, chamber_idx: 0, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 1, chamber_idx: 1, qualification: 0, process_drift: 0.0, batch_count: 0 },\n { condition: 0.32, particle_count: 0.34, hours_since_maintenance: 80.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 2, chamber_idx: 0, qualification: 1, process_drift: 0.005, batch_count: 0 },\n { condition: 0.4, particle_count: 0.4, hours_since_maintenance: 100.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 2, chamber_idx: 1, qualification: 2, process_drift: 0.01, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 1, chamber_idx: 0, qualification: 1, process_drift: 0.0, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 1, chamber_idx: 1, qualification: 2, process_drift: 0.005, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 3, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 3, tool_id: 1, chamber_idx: 0, qualification: 0, process_drift: 0.0, batch_count: 0 }\n ],\n ChambersProcessing: [],\n ChambersInMaintenance: [],\n ChambersBroken: [],\n MaintenanceCrew: 3,\n MaintenanceEvents: 0,\n BreakdownEvents: 0,\n LotsReleased: 0,\n LotsCompleted: 0,\n Calibrations: 0,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "wip_cap", + "type": "integer", + "expression": "50", + "exposed": true, + "optimize": null + }, + { + "name": "demand_rate", + "type": "real", + "expression": "0.12", + "exposed": true, + "optimize": null + }, + { + "name": "maintenance_threshold", + "type": "real", + "expression": "0.85", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__wip_cap", + "expression": "scenario.wip_cap", + "optimize": null + }, + { + "parameterId": "param__demand_rate", + "expression": "scenario.demand_rate", + "optimize": null + }, + { + "parameterId": "param__maintenance_threshold", + "expression": "scenario.maintenance_threshold", + "optimize": null + } + ], + "places": { + "place__fab_entrance": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__wip_queue": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__batch_queue": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__in_process": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__post_process": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__in_inspection": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__finished": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__scrapped": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__wip_permits": { + "kind": "uncoloured", + "count": { + "expression": "Math.max(0, parameters.wip_cap - 35)", + "optimize": null + } + }, + "place__chambers_available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.015", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.32", + "optimize": null + }, + { + "expression": "0.34", + "optimize": null + }, + { + "expression": "80.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.4", + "optimize": null + }, + { + "expression": "0.4", + "optimize": null + }, + { + "expression": "100.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__chambers_processing": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__chambers_in_maintenance": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__chambers_broken": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__maintenance_crew": { + "kind": "uncoloured", + "count": { + "expression": "3", + "optimize": null + } + }, + "place__maintenance_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__breakdown_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__lots_released": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__lots_completed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__calibrations": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1441,8 +4170,2742 @@ "param__drift_volatility": "0.04" }, "initialState": { - "type": "code", - "content": "return {\n FabEntrance: [],\n WIPQueue: [\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 21, priority: 1.0, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 21, priority: 1.0, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 }\n ],\n BatchQueue: [],\n InProcess: [],\n PostProcess: [],\n InInspection: [],\n Finished: [],\n Scrapped: [],\n WIPPermits: Math.max(0, parameters.wip_cap - 35),\n ChambersAvailable: [\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 1, chamber_idx: 0, qualification: 1, process_drift: 0.0, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 1, chamber_idx: 1, qualification: 2, process_drift: 0.005, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.015, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 1, chamber_idx: 0, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 1, chamber_idx: 1, qualification: 0, process_drift: 0.0, batch_count: 0 },\n { condition: 0.32, particle_count: 0.34, hours_since_maintenance: 80.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 2, chamber_idx: 0, qualification: 1, process_drift: 0.005, batch_count: 0 },\n { condition: 0.4, particle_count: 0.4, hours_since_maintenance: 100.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 2, chamber_idx: 1, qualification: 2, process_drift: 0.01, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 1, chamber_idx: 0, qualification: 1, process_drift: 0.0, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 1, chamber_idx: 1, qualification: 2, process_drift: 0.005, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 3, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 3, tool_id: 1, chamber_idx: 0, qualification: 0, process_drift: 0.0, batch_count: 0 }\n ],\n ChambersProcessing: [],\n ChambersInMaintenance: [],\n ChambersBroken: [],\n MaintenanceCrew: 3,\n MaintenanceEvents: 0,\n BreakdownEvents: 0,\n LotsReleased: 0,\n LotsCompleted: 0,\n Calibrations: 0,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "wip_cap", + "type": "integer", + "expression": "50", + "exposed": true, + "optimize": null + }, + { + "name": "demand_rate", + "type": "real", + "expression": "0.12", + "exposed": true, + "optimize": null + }, + { + "name": "maintenance_threshold", + "type": "real", + "expression": "0.85", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__wip_cap", + "expression": "scenario.wip_cap", + "optimize": null + }, + { + "parameterId": "param__demand_rate", + "expression": "scenario.demand_rate", + "optimize": null + }, + { + "parameterId": "param__maintenance_threshold", + "expression": "scenario.maintenance_threshold", + "optimize": null + }, + { + "parameterId": "param__drift_volatility", + "expression": "0.04", + "optimize": null + } + ], + "places": { + "place__fab_entrance": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__wip_queue": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__batch_queue": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__in_process": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__post_process": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__in_inspection": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__finished": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__scrapped": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__wip_permits": { + "kind": "uncoloured", + "count": { + "expression": "Math.max(0, parameters.wip_cap - 35)", + "optimize": null + } + }, + "place__chambers_available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.015", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.32", + "optimize": null + }, + { + "expression": "0.34", + "optimize": null + }, + { + "expression": "80.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.4", + "optimize": null + }, + { + "expression": "0.4", + "optimize": null + }, + { + "expression": "100.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__chambers_processing": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__chambers_in_maintenance": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__chambers_broken": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__maintenance_crew": { + "kind": "uncoloured", + "count": { + "expression": "3", + "optimize": null + } + }, + "place__maintenance_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__breakdown_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__lots_released": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__lots_completed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__calibrations": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1472,8 +6935,2737 @@ "param__maintenance_threshold": "scenario.maintenance_threshold" }, "initialState": { - "type": "code", - "content": "return {\n FabEntrance: [],\n WIPQueue: [\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 21, priority: 1.0, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 21, priority: 1.0, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 }\n ],\n BatchQueue: [],\n InProcess: [],\n PostProcess: [],\n InInspection: [],\n Finished: [],\n Scrapped: [],\n WIPPermits: Math.max(0, parameters.wip_cap - 35),\n ChambersAvailable: [\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 1, chamber_idx: 0, qualification: 1, process_drift: 0.0, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 1, chamber_idx: 1, qualification: 2, process_drift: 0.005, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.015, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 1, chamber_idx: 0, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 1, chamber_idx: 1, qualification: 0, process_drift: 0.0, batch_count: 0 },\n { condition: 0.32, particle_count: 0.34, hours_since_maintenance: 80.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 2, chamber_idx: 0, qualification: 1, process_drift: 0.005, batch_count: 0 },\n { condition: 0.4, particle_count: 0.4, hours_since_maintenance: 100.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 2, chamber_idx: 1, qualification: 2, process_drift: 0.01, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 1, chamber_idx: 0, qualification: 1, process_drift: 0.0, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 1, chamber_idx: 1, qualification: 2, process_drift: 0.005, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 3, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 3, tool_id: 1, chamber_idx: 0, qualification: 0, process_drift: 0.0, batch_count: 0 }\n ],\n ChambersProcessing: [],\n ChambersInMaintenance: [],\n ChambersBroken: [],\n MaintenanceCrew: 3,\n MaintenanceEvents: 0,\n BreakdownEvents: 0,\n LotsReleased: 0,\n LotsCompleted: 0,\n Calibrations: 0,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "wip_cap", + "type": "integer", + "expression": "50", + "exposed": true, + "optimize": null + }, + { + "name": "demand_rate", + "type": "real", + "expression": "0.12", + "exposed": true, + "optimize": null + }, + { + "name": "maintenance_threshold", + "type": "real", + "expression": "0.6", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__wip_cap", + "expression": "scenario.wip_cap", + "optimize": null + }, + { + "parameterId": "param__demand_rate", + "expression": "scenario.demand_rate", + "optimize": null + }, + { + "parameterId": "param__maintenance_threshold", + "expression": "scenario.maintenance_threshold", + "optimize": null + } + ], + "places": { + "place__fab_entrance": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__wip_queue": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__batch_queue": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__in_process": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__post_process": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__in_inspection": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__finished": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__scrapped": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__wip_permits": { + "kind": "uncoloured", + "count": { + "expression": "Math.max(0, parameters.wip_cap - 35)", + "optimize": null + } + }, + "place__chambers_available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.015", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.32", + "optimize": null + }, + { + "expression": "0.34", + "optimize": null + }, + { + "expression": "80.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.4", + "optimize": null + }, + { + "expression": "0.4", + "optimize": null + }, + { + "expression": "100.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__chambers_processing": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__chambers_in_maintenance": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__chambers_broken": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__maintenance_crew": { + "kind": "uncoloured", + "count": { + "expression": "3", + "optimize": null + } + }, + "place__maintenance_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__breakdown_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__lots_released": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__lots_completed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__calibrations": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1503,8 +9695,2737 @@ "param__maintenance_threshold": "scenario.maintenance_threshold" }, "initialState": { - "type": "code", - "content": "return {\n FabEntrance: [],\n WIPQueue: [\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 21, priority: 1.0, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 21, priority: 1.0, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 7, priority: 1.11, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 14, priority: 1.0, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 21, priority: 1.22, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 14, priority: 1.09, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 21, priority: 1.41, age: 52.5, defect_count: 0.378, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.063, processing_chamber: -1, batch_wait: 0 },\n { product_type: 2, layer: 0, priority: 1.0, age: 0.0, defect_count: 0.0, process_remaining: 0, wait_time: 0, due_date: 250, process_bias: 0.0, processing_chamber: -1, batch_wait: 0 },\n { product_type: 0, layer: 7, priority: 1.0, age: 17.5, defect_count: 0.126, process_remaining: 0, wait_time: 0, due_date: 200, process_bias: 0.021, processing_chamber: -1, batch_wait: 0 },\n { product_type: 1, layer: 14, priority: 1.24, age: 35.0, defect_count: 0.252, process_remaining: 0, wait_time: 0, due_date: 180, process_bias: 0.042, processing_chamber: -1, batch_wait: 0 }\n ],\n BatchQueue: [],\n InProcess: [],\n PostProcess: [],\n InInspection: [],\n Finished: [],\n Scrapped: [],\n WIPPermits: Math.max(0, parameters.wip_cap - 35),\n ChambersAvailable: [\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 1, chamber_idx: 0, qualification: 1, process_drift: 0.0, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 0, tool_id: 1, chamber_idx: 1, qualification: 2, process_drift: 0.005, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.015, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 1, chamber_idx: 0, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 1, chamber_idx: 1, qualification: 0, process_drift: 0.0, batch_count: 0 },\n { condition: 0.32, particle_count: 0.34, hours_since_maintenance: 80.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 2, chamber_idx: 0, qualification: 1, process_drift: 0.005, batch_count: 0 },\n { condition: 0.4, particle_count: 0.4, hours_since_maintenance: 100.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 1, tool_id: 2, chamber_idx: 1, qualification: 2, process_drift: 0.01, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.01, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 0, chamber_idx: 1, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.16, particle_count: 0.22, hours_since_maintenance: 40.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 1, chamber_idx: 0, qualification: 1, process_drift: 0.0, batch_count: 0 },\n { condition: 0.24, particle_count: 0.28, hours_since_maintenance: 60.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 2, tool_id: 1, chamber_idx: 1, qualification: 2, process_drift: 0.005, batch_count: 0 },\n { condition: 0.0, particle_count: 0.1, hours_since_maintenance: 0.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 3, tool_id: 0, chamber_idx: 0, qualification: 0, process_drift: -0.005, batch_count: 0 },\n { condition: 0.08, particle_count: 0.16, hours_since_maintenance: 20.0, maintenance_remaining: 0, diffusion_clock: 0, lots_processed: 0, machine_group: 3, tool_id: 1, chamber_idx: 0, qualification: 0, process_drift: 0.0, batch_count: 0 }\n ],\n ChambersProcessing: [],\n ChambersInMaintenance: [],\n ChambersBroken: [],\n MaintenanceCrew: 2,\n MaintenanceEvents: 0,\n BreakdownEvents: 0,\n LotsReleased: 0,\n LotsCompleted: 0,\n Calibrations: 0,\n};" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "wip_cap", + "type": "integer", + "expression": "50", + "exposed": true, + "optimize": null + }, + { + "name": "demand_rate", + "type": "real", + "expression": "0.12", + "exposed": true, + "optimize": null + }, + { + "name": "maintenance_threshold", + "type": "real", + "expression": "0.85", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__wip_cap", + "expression": "scenario.wip_cap", + "optimize": null + }, + { + "parameterId": "param__demand_rate", + "expression": "scenario.demand_rate", + "optimize": null + }, + { + "parameterId": "param__maintenance_threshold", + "expression": "scenario.maintenance_threshold", + "optimize": null + } + ], + "places": { + "place__fab_entrance": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__wip_queue": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.11", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.22", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.09", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "21", + "optimize": null + }, + { + "expression": "1.41", + "optimize": null + }, + { + "expression": "52.5", + "optimize": null + }, + { + "expression": "0.378", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.063", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "250", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0", + "optimize": null + }, + { + "expression": "7", + "optimize": null + }, + { + "expression": "1.0", + "optimize": null + }, + { + "expression": "17.5", + "optimize": null + }, + { + "expression": "0.126", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "200", + "optimize": null + }, + { + "expression": "0.021", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "1", + "optimize": null + }, + { + "expression": "14", + "optimize": null + }, + { + "expression": "1.24", + "optimize": null + }, + { + "expression": "35.0", + "optimize": null + }, + { + "expression": "0.252", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "180", + "optimize": null + }, + { + "expression": "0.042", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-1", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__batch_queue": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__in_process": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__post_process": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__in_inspection": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__finished": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__scrapped": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__wip_permits": { + "kind": "uncoloured", + "count": { + "expression": "Math.max(0, parameters.wip_cap - 35)", + "optimize": null + } + }, + "place__chambers_available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.015", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.32", + "optimize": null + }, + { + "expression": "0.34", + "optimize": null + }, + { + "expression": "80.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.4", + "optimize": null + }, + { + "expression": "0.4", + "optimize": null + }, + { + "expression": "100.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.01", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "0.22", + "optimize": null + }, + { + "expression": "40.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.24", + "optimize": null + }, + { + "expression": "0.28", + "optimize": null + }, + { + "expression": "60.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "2", + "optimize": null + }, + { + "expression": "0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0.1", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "-0.005", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + }, + { + "kind": "fixed", + "cells": [ + { + "expression": "0.08", + "optimize": null + }, + { + "expression": "0.16", + "optimize": null + }, + { + "expression": "20.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "3", + "optimize": null + }, + { + "expression": "1", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0.0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__chambers_processing": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__chambers_in_maintenance": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__chambers_broken": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__maintenance_crew": { + "kind": "uncoloured", + "count": { + "expression": "2", + "optimize": null + } + }, + "place__maintenance_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__breakdown_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__lots_released": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__lots_completed": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + }, + "place__calibrations": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } } ], diff --git a/apps/petrinaut-website/src/examples/models/truck-fleet-predictive-maintenance.json b/apps/petrinaut-website/src/examples/models/truck-fleet-predictive-maintenance.json index 00cecb15179..12f0901207a 100644 --- a/apps/petrinaut-website/src/examples/models/truck-fleet-predictive-maintenance.json +++ b/apps/petrinaut-website/src/examples/models/truck-fleet-predictive-maintenance.json @@ -1598,8 +1598,239 @@ "param__service_wear_limit": "scenario.service_wear_limit" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: parameters.base_severity_mean,\n speed_factor: parameters.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: parameters.base_severity_mean, speed_mean: parameters.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "9", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1647,8 +1878,239 @@ "param__service_wear_limit": "scenario.service_wear_limit" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: parameters.base_severity_mean,\n speed_factor: parameters.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: parameters.base_severity_mean, speed_mean: parameters.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.5", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1696,8 +2158,239 @@ "param__service_wear_limit": "scenario.service_wear_limit" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: parameters.base_severity_mean,\n speed_factor: parameters.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: parameters.base_severity_mean, speed_mean: parameters.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.25", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1751,8 +2444,251 @@ "param__severe_route_wear_limit": "scenario.severe_route_wear_limit" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: parameters.base_severity_mean,\n speed_factor: parameters.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: parameters.base_severity_mean, speed_mean: parameters.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.5", + "exposed": true, + "optimize": null + }, + { + "name": "severe_route_wear_limit", + "type": "real", + "expression": "0.35", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + }, + { + "parameterId": "param__severe_route_wear_limit", + "expression": "scenario.severe_route_wear_limit", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1800,8 +2736,239 @@ "param__service_wear_limit": "scenario.service_wear_limit" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: parameters.base_severity_mean,\n speed_factor: parameters.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: parameters.base_severity_mean, speed_mean: parameters.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "1", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.5", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1855,8 +3022,251 @@ "param__parts_lead_time": "scenario.parts_lead_time" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: parameters.base_severity_mean,\n speed_factor: parameters.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: parameters.base_severity_mean, speed_mean: parameters.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "1", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.5", + "exposed": true, + "optimize": null + }, + { + "name": "parts_lead_time", + "type": "real", + "expression": "72", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + }, + { + "parameterId": "param__parts_lead_time", + "expression": "scenario.parts_lead_time", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1916,8 +3326,263 @@ "param__base_speed_mean": "scenario.base_speed_mean" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: scenario.base_severity_mean,\n speed_factor: scenario.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: scenario.base_severity_mean, speed_mean: scenario.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.5", + "exposed": true, + "optimize": null + }, + { + "name": "base_severity_mean", + "type": "real", + "expression": "1.4", + "exposed": true, + "optimize": null + }, + { + "name": "base_speed_mean", + "type": "real", + "expression": "0.8", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + }, + { + "parameterId": "param__base_severity_mean", + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "parameterId": "param__base_speed_mean", + "expression": "scenario.base_speed_mean", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "expression": "scenario.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "expression": "scenario.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -1977,8 +3642,263 @@ "param__base_speed_mean": "scenario.base_speed_mean" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: scenario.base_severity_mean,\n speed_factor: scenario.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: scenario.base_severity_mean, speed_mean: scenario.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.5", + "exposed": true, + "optimize": null + }, + { + "name": "base_severity_mean", + "type": "real", + "expression": "0.9", + "exposed": true, + "optimize": null + }, + { + "name": "base_speed_mean", + "type": "real", + "expression": "1.1", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + }, + { + "parameterId": "param__base_severity_mean", + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "parameterId": "param__base_speed_mean", + "expression": "scenario.base_speed_mean", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "expression": "scenario.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "expression": "scenario.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -2044,8 +3964,275 @@ "param__mountain_rate": "scenario.mountain_rate" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: parameters.base_severity_mean,\n speed_factor: parameters.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: parameters.base_severity_mean, speed_mean: parameters.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.5", + "exposed": true, + "optimize": null + }, + { + "name": "motorway_rate", + "type": "real", + "expression": "0.105", + "exposed": true, + "optimize": null + }, + { + "name": "urban_rate", + "type": "real", + "expression": "0.1425", + "exposed": true, + "optimize": null + }, + { + "name": "mountain_rate", + "type": "real", + "expression": "0.072", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + }, + { + "parameterId": "param__motorway_rate", + "expression": "scenario.motorway_rate", + "optimize": null + }, + { + "parameterId": "param__urban_rate", + "expression": "scenario.urban_rate", + "optimize": null + }, + { + "parameterId": "param__mountain_rate", + "expression": "scenario.mountain_rate", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "parameters.base_severity_mean", + "optimize": null + }, + { + "expression": "parameters.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -2123,8 +4310,299 @@ "param__mountain_rate": "scenario.mountain_rate" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: scenario.base_severity_mean,\n speed_factor: scenario.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: scenario.base_severity_mean, speed_mean: scenario.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.5", + "exposed": true, + "optimize": null + }, + { + "name": "base_severity_mean", + "type": "real", + "expression": "1.4", + "exposed": true, + "optimize": null + }, + { + "name": "base_speed_mean", + "type": "real", + "expression": "0.8", + "exposed": true, + "optimize": null + }, + { + "name": "motorway_rate", + "type": "real", + "expression": "0.105", + "exposed": true, + "optimize": null + }, + { + "name": "urban_rate", + "type": "real", + "expression": "0.1425", + "exposed": true, + "optimize": null + }, + { + "name": "mountain_rate", + "type": "real", + "expression": "0.072", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + }, + { + "parameterId": "param__base_severity_mean", + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "parameterId": "param__base_speed_mean", + "expression": "scenario.base_speed_mean", + "optimize": null + }, + { + "parameterId": "param__motorway_rate", + "expression": "scenario.motorway_rate", + "optimize": null + }, + { + "parameterId": "param__urban_rate", + "expression": "scenario.urban_rate", + "optimize": null + }, + { + "parameterId": "param__mountain_rate", + "expression": "scenario.mountain_rate", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "expression": "scenario.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "expression": "scenario.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } }, { @@ -2190,8 +4668,275 @@ "param__base_speed_mean": "scenario.base_speed_mean" }, "initialState": { - "type": "code", - "content": "const fleet = range(scenario.trucks).map((index) => ({\n brake_wear: (index / scenario.trucks) * 0.1,\n engine_wear: (index / scenario.trucks) * 0.08,\n tyre_wear: (index / scenario.trucks) * 0.12,\n km_remaining: 0,\n route_distance: 0,\n service_remaining: 0,\n route_class: 0,\n load_due: 0,\n load_revenue: 0,\n age: 0,\n loads_done: 0,\n unplanned: 0,\n road_severity: scenario.base_severity_mean,\n speed_factor: scenario.base_speed_mean,\n conditions_clock: 0,\n fuel_burned: 0,\n hours_driven: 0,\n rest_remaining: 0,\n fuel_rate: 0,\n }));\n return {\n Available: fleet,\n LoadBoard: [],\n Drivers: scenario.drivers,\n Bays: scenario.bays,\n Technicians: scenario.technicians,\n Spares: scenario.spares,\n RecoveryUnits: scenario.recovery_units,\n Conditions: [{ severity_mean: scenario.base_severity_mean, speed_mean: scenario.base_speed_mean, clock: 0 }],\n Rest: [],\n RestEvents: 0,\n };" + "type": "adhoc", + "content": { + "variables": [ + { + "name": "trucks", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "drivers", + "type": "integer", + "expression": "8", + "exposed": true, + "optimize": null + }, + { + "name": "bays", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "technicians", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "spares", + "type": "integer", + "expression": "10", + "exposed": true, + "optimize": null + }, + { + "name": "recovery_units", + "type": "integer", + "expression": "2", + "exposed": true, + "optimize": null + }, + { + "name": "service_wear_limit", + "type": "real", + "expression": "0.5", + "exposed": true, + "optimize": null + }, + { + "name": "severe_route_wear_limit", + "type": "real", + "expression": "0.35", + "exposed": true, + "optimize": null + }, + { + "name": "base_severity_mean", + "type": "real", + "expression": "1.4", + "exposed": true, + "optimize": null + }, + { + "name": "base_speed_mean", + "type": "real", + "expression": "0.8", + "exposed": true, + "optimize": null + } + ], + "netParameters": [ + { + "parameterId": "param__service_wear_limit", + "expression": "scenario.service_wear_limit", + "optimize": null + }, + { + "parameterId": "param__severe_route_wear_limit", + "expression": "scenario.severe_route_wear_limit", + "optimize": null + }, + { + "parameterId": "param__base_severity_mean", + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "parameterId": "param__base_speed_mean", + "expression": "scenario.base_speed_mean", + "optimize": null + } + ], + "places": { + "place__available": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "template", + "count": { + "expression": "scenario.trucks", + "optimize": null + }, + "cells": [ + { + "expression": "(i / scenario.trucks) * 0.1", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.08", + "optimize": null + }, + { + "expression": "(i / scenario.trucks) * 0.12", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "expression": "scenario.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__load_board": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__drivers": { + "kind": "uncoloured", + "count": { + "expression": "scenario.drivers", + "optimize": null + } + }, + "place__bays": { + "kind": "uncoloured", + "count": { + "expression": "scenario.bays", + "optimize": null + } + }, + "place__technicians": { + "kind": "uncoloured", + "count": { + "expression": "scenario.technicians", + "optimize": null + } + }, + "place__spares": { + "kind": "uncoloured", + "count": { + "expression": "scenario.spares", + "optimize": null + } + }, + "place__recovery": { + "kind": "uncoloured", + "count": { + "expression": "scenario.recovery_units", + "optimize": null + } + }, + "place__conditions": { + "kind": "coloured", + "variables": [], + "rows": [ + { + "kind": "fixed", + "cells": [ + { + "expression": "scenario.base_severity_mean", + "optimize": null + }, + { + "expression": "scenario.base_speed_mean", + "optimize": null + }, + { + "expression": "0", + "optimize": null + } + ] + } + ], + "sharedColumns": {} + }, + "place__rest": { + "kind": "coloured", + "variables": [], + "rows": [], + "sharedColumns": {} + }, + "place__rest_events": { + "kind": "uncoloured", + "count": { + "expression": "0", + "optimize": null + } + } + } + } } } ], diff --git a/apps/petrinaut-website/src/examples/navigation-search.test.ts b/apps/petrinaut-website/src/examples/navigation-search.test.ts index b691d6e1b5b..46a52cd0979 100644 --- a/apps/petrinaut-website/src/examples/navigation-search.test.ts +++ b/apps/petrinaut-website/src/examples/navigation-search.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "vitest"; -import { sharedOverlays, sharedSimulateViews } from "./example-search"; +import { + sharedOverlays, + sharedSimulateViews, + validateSharedExampleSearch, +} from "./example-search"; import { applyPreviewNavigationUpdate, navigationStateToSharedSearch, @@ -8,7 +12,94 @@ import { } from "./navigation-search"; describe("navigation state projection", () => { - it.each(["general", "viewport", "simulation", "labs"] as const)( + it("routes Definitions within Edit and preserves the selection in shared links", () => { + const search = { + editView: "definitions", + itemType: "transition", + itemId: "collision", + } as const; + const state = sharedSearchToNavigationState(search); + expect(state.mode).toBe("edit"); + expect(state.editView).toBe("definitions"); + expect(navigationStateToSharedSearch(state)).toMatchObject(search); + expect( + applyPreviewNavigationUpdate(search, (current) => current), + ).toMatchObject(search); + expect(sharedSearchToNavigationState({}).editView).toBe("canvas"); + }); + + it.each([{ mode: "notebook" }, { editView: "notebook" }])( + "opens legacy Notebook links in the Edit workspace: %s", + (legacy) => { + const search = validateSharedExampleSearch({ + ...legacy, + itemType: "place", + itemId: "space", + }); + expect(search).toMatchObject({ + editView: "definitions", + itemId: "space", + }); + expect(sharedSearchToNavigationState(search)).toMatchObject({ + mode: "edit", + editView: "definitions", + }); + }, + ); + + it("round-trips an expanded Properties Panel section with its selected item", () => { + const search = { + itemType: "transition", + itemId: "collision", + expandedPanel: "transition-properties", + expandedSection: "transition-results", + } as const; + const state = sharedSearchToNavigationState(search); + expect(state.expandedSubView).toEqual({ + container: search.expandedPanel, + id: search.expandedSection, + }); + expect(navigationStateToSharedSearch(state)).toMatchObject(search); + expect( + sharedSearchToNavigationState({ + itemType: "transition", + itemId: "collision", + }).expandedSubView, + ).toBeNull(); + expect( + applyPreviewNavigationUpdate(search, (current) => current), + ).toMatchObject(search); + }); + + it.each(["scenario", "experiment"] as const)( + "opens a direct %s link and preserves its presentation through Preview", + (resourceType) => { + const search = { + resourceType, + resourceId: "record / one", + presentation: "fullscreen" as const, + }; + const state = sharedSearchToNavigationState(search); + expect(state.mode).toBe("simulate"); + expect(state.simulateView).toBe( + resourceType === "scenario" ? "scenarios" : "experiments", + ); + expect(state.simulateResource).toEqual({ + type: resourceType, + id: "record / one", + }); + expect(state.simulatePresentation).toBe("fullscreen"); + expect(navigationStateToSharedSearch(state)).toMatchObject(search); + expect( + applyPreviewNavigationUpdate(search, (current) => ({ + ...current, + subnetId: "subnet", + })), + ).toMatchObject(search); + }, + ); + + it.each(["general", "viewport", "labs"] as const)( "round-trips the %s settings section in Simulate", (settings) => { const search = { diff --git a/apps/petrinaut-website/src/examples/navigation-search.ts b/apps/petrinaut-website/src/examples/navigation-search.ts index 7a009ee4148..ab8689a79e4 100644 --- a/apps/petrinaut-website/src/examples/navigation-search.ts +++ b/apps/petrinaut-website/src/examples/navigation-search.ts @@ -1,10 +1,9 @@ /** * Projects the example URL contract onto Petrinaut's navigation state. * - * The URL carries the location a reader can act on: the scenario, the subnet, - * the focused item, the editor's mode, its Simulate section and the overlay it - * has open. It deliberately leaves out `simulateResource`, which names a run - * or a record inside the open document rather than a place in the app. + * The URL carries the selected scenario, subnet, focused item, expanded + * properties section, editor mode, Simulate section, open record, overlay + * and panel presentation. * * Every field is decoded against a BASELINE — the location its page starts * from. A URL that does not name a field means "the baseline's value", which is @@ -18,6 +17,7 @@ import { selectionFromInput, selectionToSearch, type SharedExampleSearch, + type SharedEditView, type SharedMode, type SharedOverlay, type SharedSimulateView, @@ -26,6 +26,7 @@ import { import type { PetrinautPreviewNavigationState } from "@hashintel/petrinaut/preview"; import type { EditorGlobalMode, + EditViewMode, PetrinautNavigationOverlay, PetrinautNavigationState, PetrinautNavigationUpdater, @@ -60,6 +61,8 @@ const scenarioToSearch = ( * editor fails this file's type check until the contract decides whether the * URL should carry it. */ +const editViewToSearch = (view: EditViewMode): SharedEditView => view; + const modeToSearch = (mode: EditorGlobalMode): SharedMode => mode; const simulateViewToSearch = (view: SimulateViewMode): SharedSimulateView => @@ -83,8 +86,27 @@ export const sharedSearchToNavigationState = ( scenarioId: scenarioFromSearch(search), subnetId: search.subnet ?? null, selection: selectionFromInput(search as Record), - mode: search.mode ?? baseline.mode, - simulateView: search.view ?? baseline.simulateView, + editView: search.editView ?? baseline.editView, + expandedSubView: + search.expandedPanel && search.expandedSection + ? { container: search.expandedPanel, id: search.expandedSection } + : null, + mode: + search.mode ?? + (search.resourceType && search.resourceId ? "simulate" : baseline.mode), + simulateView: + search.resourceType && search.resourceId + ? search.resourceType === "scenario" + ? "scenarios" + : search.resourceType === "experiment" + ? "experiments" + : "metrics" + : (search.view ?? baseline.simulateView), + simulateResource: + search.resourceType && search.resourceId + ? { type: search.resourceType, id: search.resourceId } + : baseline.simulateResource, + simulatePresentation: search.presentation ?? baseline.simulatePresentation, overlay: search.overlay === undefined ? baseline.overlay @@ -96,14 +118,30 @@ export const navigationStateToSharedSearch = ( baseline: PetrinautNavigationState = defaultPetrinautNavigationState, ): SharedExampleSearch => { const mode = modeToSearch(state.mode); + const editView = editViewToSearch(state.editView); const view = simulateViewToSearch(state.simulateView); const overlay = overlayToSearch(state.overlay); + const canExpand = + state.simulateResource?.type === "scenario" || + state.simulateResource?.type === "experiment" || + overlay === "create-scenario" || + overlay === "create-experiment"; return { + resourceType: state.simulateResource?.type, + resourceId: state.simulateResource?.id, + presentation: + canExpand && state.simulatePresentation === "fullscreen" + ? "fullscreen" + : undefined, scenario: scenarioToSearch(state.scenarioId), subnet: state.subnetId ?? undefined, + expandedPanel: state.expandedSubView?.container, + expandedSection: state.expandedSubView?.id, // Omitted at the baseline, so an untouched page keeps a clean URL and the // decode above puts the baseline back. mode: mode === modeToSearch(baseline.mode) ? undefined : mode, + editView: + editView === editViewToSearch(baseline.editView) ? undefined : editView, view: view === simulateViewToSearch(baseline.simulateView) ? undefined : view, overlay: @@ -143,9 +181,15 @@ export const applyPreviewNavigationUpdate = ( update: PetrinautNavigationUpdater, ): SharedExampleSearch => ({ mode: search.mode, + editView: search.editView, view: search.view, overlay: search.overlay, settings: search.settings, + expandedPanel: search.expandedPanel, + expandedSection: search.expandedSection, + resourceType: search.resourceType, + resourceId: search.resourceId, + presentation: search.presentation, ...navigationStateToPreviewSearch( update(previewSearchToNavigationState(search)), ), diff --git a/apps/petrinaut-website/src/examples/use-shared-search-navigation.test.tsx b/apps/petrinaut-website/src/examples/use-shared-search-navigation.test.tsx index 36812ea7ae7..6c81562aaaa 100644 --- a/apps/petrinaut-website/src/examples/use-shared-search-navigation.test.tsx +++ b/apps/petrinaut-website/src/examples/use-shared-search-navigation.test.tsx @@ -3,7 +3,10 @@ import { act, render } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; -import { useSharedSearchNavigation } from "./use-shared-search-navigation"; +import { + useSharedSearchNavigation, + withClearedSharedLocation, +} from "./use-shared-search-navigation"; import type { SharedExampleSearch } from "./example-search"; import type { @@ -34,101 +37,182 @@ const Probe = ({ }; describe("useSharedSearchNavigation", () => { - it("keeps URL-unrepresentable state in memory and mirrors the shared subset", () => { + it("pushes Edit view switches and follows Back and Forward without changing selection", () => { let controller!: PetrinautNavigationController; const onSearchChange = vi.fn(); - render( - { - controller = value; - }} - onSearchChange={onSearchChange} - search={{ scenario: "scenario-1" }} - />, - ); - - // The resource open inside Simulate is the one location field the URL does - // not carry: it applies in memory and produces no URL write. - act(() => { - controller.onNavigate( - (current) => ({ - ...current, - simulateResource: { type: "experiment", id: "experiment-1" }, - }), - { - history: "push", - intent: { cause: "user", action: "simulation-resource" }, - }, - ); - }); - expect(controller.state.simulateResource).toEqual({ - type: "experiment", - id: "experiment-1", - }); - expect(onSearchChange).not.toHaveBeenCalled(); - - // A subnet change is shared: it applies in memory AND writes the URL. - act(() => { + const parent = { itemType: "transition", itemId: "collision" } as const; + const notebook = { ...parent, editView: "definitions" } as const; + const onController = (value: PetrinautNavigationController) => { + controller = value; + }; + const props = { onController, onSearchChange }; + const view = render(); + act(() => controller.onNavigate( - (current) => ({ ...current, subnetId: "subnet-1" }), - { history: "push", intent: { cause: "user", action: "subnet" } }, - ); - }); - expect(controller.state.subnetId).toBe("subnet-1"); - expect(controller.state.simulateResource).toEqual({ - type: "experiment", - id: "experiment-1", - }); - expect(onSearchChange).toHaveBeenCalledOnce(); + (current) => ({ ...current, editView: "definitions" }), + { history: "push", intent: { cause: "user", action: "edit-view" } }, + ), + ); expect(onSearchChange).toHaveBeenCalledWith( - { scenario: "scenario-1", subnet: "subnet-1" }, + expect.objectContaining(notebook), "push", ); + view.rerender(); + view.rerender(); + expect(controller.state.editView).toBe("canvas"); + view.rerender(); + expect(controller.state.editView).toBe("definitions"); + expect(controller.state.mode).toBe("edit"); + expect(controller.state.selection).toEqual([ + { type: "transition", id: "collision" }, + ]); + expect(onSearchChange).toHaveBeenCalledOnce(); }); - it("merges an external URL change without resetting in-memory fields", () => { + it("writes section expansion to history and follows Back, Forward, and document replacement", () => { let controller!: PetrinautNavigationController; const onSearchChange = vi.fn(); + const parent = { itemType: "transition", itemId: "collision" } as const; + const expanded = { + ...parent, + expandedPanel: "transition-properties", + expandedSection: "transition-results", + }; + const onController = (value: PetrinautNavigationController) => { + controller = value; + }; const view = render( { - controller = value; - }} + onController={onController} onSearchChange={onSearchChange} - search={{ scenario: "scenario-1" }} + search={parent} />, ); - - act(() => { + act(() => controller.onNavigate( (current) => ({ ...current, - simulateResource: { type: "experiment", id: "experiment-1" }, + expandedSubView: { + container: "transition-properties", + id: "transition-results", + }, }), - { - history: "push", - intent: { cause: "user", action: "simulation-resource" }, - }, - ); - }); - - // Back/Forward delivers a different shared search: URL-owned fields - // update, and the one field the URL cannot carry survives. + { history: "push", intent: { cause: "user", action: "subview" } }, + ), + ); + expect(onSearchChange).toHaveBeenCalledWith( + expect.objectContaining(expanded), + "push", + ); view.rerender( { - controller = value; - }} + onController={onController} onSearchChange={onSearchChange} - search={{ scenario: "scenario-2" }} + search={expanded} />, ); - expect(controller.state.scenarioId).toBe("scenario-2"); - expect(controller.state.simulateResource).toEqual({ - type: "experiment", - id: "experiment-1", + view.rerender( + , + ); + expect(controller.state.expandedSubView).toBeNull(); + view.rerender( + , + ); + expect(controller.state.expandedSubView).toEqual({ + container: "transition-properties", + id: "transition-results", }); + expect(onSearchChange).toHaveBeenCalledOnce(); + expect( + withClearedSharedLocation(controller.state).expandedSubView, + ).toBeNull(); }); + it.each(["experiment", "scenario"] as const)( + "records the open %s and restores drawer/fullscreen with Back and Forward", + (resourceType) => { + let controller!: PetrinautNavigationController; + const onSearchChange = vi.fn(); + const probe = (search: SharedExampleSearch) => ( + { + controller = value; + }} + onSearchChange={onSearchChange} + search={search} + /> + ); + const view = render(probe({})); + act(() => + controller.onNavigate( + (current) => ({ + ...current, + mode: "simulate", + simulateView: + resourceType === "scenario" ? "scenarios" : "experiments", + simulateResource: { type: resourceType, id: "record-1" }, + }), + { + history: "push", + intent: { cause: "user", action: "simulation-resource" }, + }, + ), + ); + const drawerSearch: SharedExampleSearch = { + mode: "simulate", + view: resourceType === "scenario" ? "scenarios" : undefined, + resourceType, + resourceId: "record-1", + }; + expect(onSearchChange).toHaveBeenLastCalledWith( + expect.objectContaining(drawerSearch), + "push", + ); + view.rerender(probe(drawerSearch)); + + act(() => + controller.onNavigate( + (current) => ({ ...current, simulatePresentation: "fullscreen" }), + { + history: "push", + intent: { cause: "user", action: "simulation-presentation" }, + }, + ), + ); + const fullscreenSearch = { + ...drawerSearch, + presentation: "fullscreen" as const, + }; + expect(onSearchChange).toHaveBeenLastCalledWith( + expect.objectContaining(fullscreenSearch), + "push", + ); + view.rerender(probe(fullscreenSearch)); + view.rerender(probe(drawerSearch)); + expect(controller.state.simulatePresentation ?? "panel").toBe("panel"); + expect(controller.state.simulateResource).toEqual({ + type: resourceType, + id: "record-1", + }); + view.rerender(probe({})); + expect(controller.state.simulateResource).toBeNull(); + view.rerender(probe(drawerSearch)); + view.rerender(probe(fullscreenSearch)); + expect(controller.state.simulatePresentation).toBe("fullscreen"); + expect(controller.state.simulateResource).toEqual({ + type: resourceType, + id: "record-1", + }); + expect(onSearchChange).toHaveBeenCalledTimes(2); + }, + ); it("returns a URL-owned field to the baseline when Back drops it", () => { let controller!: PetrinautNavigationController; diff --git a/apps/petrinaut-website/src/examples/use-shared-search-navigation.ts b/apps/petrinaut-website/src/examples/use-shared-search-navigation.ts index f3e0b7de177..539b9e13773 100644 --- a/apps/petrinaut-website/src/examples/use-shared-search-navigation.ts +++ b/apps/petrinaut-website/src/examples/use-shared-search-navigation.ts @@ -35,8 +35,12 @@ const mergeSharedSearch = ( scenarioId: shared.scenarioId, subnetId: shared.subnetId, selection: shared.selection, + expandedSubView: shared.expandedSubView, mode: shared.mode, + editView: shared.editView, simulateView: shared.simulateView, + simulateResource: shared.simulateResource, + simulatePresentation: shared.simulatePresentation, overlay: shared.overlay, }; }; @@ -58,14 +62,16 @@ export const withClearedSharedLocation = ( scenarioId: undefined, subnetId: null, selection: [], + expandedSubView: null, + simulateResource: null, + simulatePresentation: undefined, }); /** * Navigation controller for pages whose URL carries the shared location: the - * scenario, the subnet, the focused item, the mode, the Simulate section and - * the open overlay. The editor navigates one field more than that — the - * resource open inside Simulate — so the full location still lives in page - * state and only its shared projection reaches the URL. + * selected scenario, subnet, focused item, expanded properties section, + * mode, Simulate section, open record, overlay and panel presentation. + * Page state also retains multi-selection. * * `initialState` is the location this page starts from, for every field the URL * does not name; the URL overrides whatever it does name. A controlled host diff --git a/apps/petrinaut-website/src/main/app/ai-experiments-demo/ai-experiments-demo.tsx b/apps/petrinaut-website/src/main/app/ai-experiments-demo/ai-experiments-demo.tsx index b88ffa011d1..66a33fcdd07 100644 --- a/apps/petrinaut-website/src/main/app/ai-experiments-demo/ai-experiments-demo.tsx +++ b/apps/petrinaut-website/src/main/app/ai-experiments-demo/ai-experiments-demo.tsx @@ -14,7 +14,7 @@ const introduction: PetrinautAiMessage[] = [ parts: [ { type: "text", - text: "Let's explore an illustrative outbreak model. Ask how to compare its runs or search different starting infection levels.\n\nFor optimization, enable **Parameter sweeps** and **In-browser optimization** in Settings → Simulation.", + text: "Let's explore an illustrative outbreak model. Ask how to compare its runs or search different starting infection levels.", }, ], }, diff --git a/apps/petrinaut-website/src/main/app/optimization-demo/README.md b/apps/petrinaut-website/src/main/app/optimization-demo/README.md index 82745f0c59c..fd29f09e6f1 100644 --- a/apps/petrinaut-website/src/main/app/optimization-demo/README.md +++ b/apps/petrinaut-website/src/main/app/optimization-demo/README.md @@ -9,5 +9,4 @@ role: "Optimization host: the in-browser runtime the demo mounts around the edit around `LocalStorageDemoApp`: `createBrowserOptimization()` from `@hashintel/petrinaut-core/browser-optimization`, which runs the Optuna study in a Pyodide web worker. The main demo (`routes/index.tsx`) mounts it, and -Petrinaut connects it while the experimental **In-browser optimization** -setting is on. +Petrinaut connects it when the first study starts. diff --git a/apps/petrinaut-website/src/main/app/optimization-demo/browser-optimization-provider.tsx b/apps/petrinaut-website/src/main/app/optimization-demo/browser-optimization-provider.tsx index a5ec056c5bc..b2a550b5bec 100644 --- a/apps/petrinaut-website/src/main/app/optimization-demo/browser-optimization-provider.tsx +++ b/apps/petrinaut-website/src/main/app/optimization-demo/browser-optimization-provider.tsx @@ -8,8 +8,7 @@ const browserOptimization = createBrowserOptimization(); /** * The in-tab optimizer: the Optuna study runs in a Pyodide worker and each * step runs on the editor's own experiments backend, so the demo needs no - * optimizer service. Petrinaut connects it only while its experimental - * In-browser optimization setting is on. + * optimizer service. Petrinaut connects it when the first study starts. */ export const BrowserOptimizationProvider: FC = ({ children, diff --git a/libs/@hashintel/petrinaut-cli/schemas/optimization-protocol.schema.json b/libs/@hashintel/petrinaut-cli/schemas/optimization-protocol.schema.json index b0baee1b2a9..4a4c5882e2f 100644 --- a/libs/@hashintel/petrinaut-cli/schemas/optimization-protocol.schema.json +++ b/libs/@hashintel/petrinaut-cli/schemas/optimization-protocol.schema.json @@ -1188,7 +1188,7 @@ }, "required": ["space", "id", "code", "hir"], "additionalProperties": false, - "description": "One boolean condition over the simulation state, authored like a metric body but returning boolean, e.g. `return state.places.Queue.count <= 10;`. Observed while a run goes." + "description": "One boolean condition over the simulation state, e.g. `state.places.Queue.count <= 10`. A function body with an explicit return is also accepted. Observed while a run goes." }, "StateConstraintHir": { "type": "object", diff --git a/libs/@hashintel/petrinaut-core/src/ai.ts b/libs/@hashintel/petrinaut-core/src/ai.ts index 894cb7126d7..5b1470a108e 100644 --- a/libs/@hashintel/petrinaut-core/src/ai.ts +++ b/libs/@hashintel/petrinaut-core/src/ai.ts @@ -104,10 +104,12 @@ export const petrinautDocNames = [ "scenarios", "ad-hoc-scenarios", "experiments", + "simulation-panels", "actual-mode", "preview", "ai-assistant", "visual-settings", + "code-editor", "compilation-output", "examples", ] as const; @@ -126,17 +128,21 @@ export const petrinautDocSummaries: Record = { scenarios: "Named simulation configurations authored through the scenario form: Variables exposed as scenario parameters, parameter overrides, per-place initial state blocks, running and switching scenarios, the expression language, scenarios stored per place or as code by files, the AI or earlier versions.", "ad-hoc-scenarios": - "Inline initial state + parameters without saving a scenario: the shared form (scenario. variables, fixed/dynamic/swept-count rows chosen from the row gutter's menu, shared columns, phantom row, place totals, live type checking), its three surfaces (quick simulation, experiments, scenario creation and editing with Scenario Parameter toggles), interval selections — Sweep or Optimize by setting — with generated adhoc_* parameter names, saved scenarios shown in run mode.", + "Inline initial state + parameters without saving a scenario: the shared form (scenario. variables, fixed/dynamic/swept-count rows chosen from the row gutter's menu, shared columns, phantom row, place totals, live type checking), its three surfaces (quick simulation, experiments, scenario creation and editing with Scenario Parameter toggles), Sweep interval selections with generated adhoc_* parameter names, saved scenarios shown in run mode.", experiments: - "Monte Carlo batches: configuration (runs, seed, dt, max time, scenario), parameter sweeps, constraints (parameter and state, pass threshold), Optimize toggles and an Objective section (metric, direction, steps) at creation, the drawer opening already optimizing, Stop on the Parameters card, one study per experiment, lifecycle/statuses, cancel/remove, header columns (Steps, Steps clear), metric charts, the Constraints and Sensitivity analysis cards, the steps table, Objective by step, compute backend, active-experiments popover.", + "Monte Carlo batches: configuration (runs, seed, dt, max time, scenario), parameter sweeps, constraints (parameter and state, pass threshold), metric objectives (metric, direction, steps), optional optimizer startup at creation or later from Parameters, Stop and restart, one study per experiment, lifecycle/statuses, cancel/remove, progress and Details, metric charts, the Constraints and Sensitivity analysis cards, the steps table, Objective by step, compute backend, active-experiments popover.", + "simulation-panels": + "Experiment and scenario panels, fullscreen controls, state preservation, docked and floating AI layout, links and browser history, session limits for experiments.", "actual-mode": "Actual mode: host-provided live execution view, Brunch stream URL route, read-only extension-free net, current limits.", preview: "Compact read-only PetrinautPreview for host-controlled embeds: shared SDCPN canvas, pan/zoom/fit/minimap, selection and responsive inspector, root/subnet navigation, URL-state ownership, omitted editing and management UI, and host-owned iframe security.", "ai-assistant": "In-app AI assistant: opening the panel, one text and Voice mode transcript/composer, waveform start, inline Voice state and provenance, typed handoff, consent/recovery, prompt chips, tool cards, read-only/simulate-mode rules, host configuration.", + "code-editor": + "Code editing in the Properties Panel: expand a section to fill the panel, return to the item’s other properties, direct function navigation, automatic edits and read-only behavior.", "visual-settings": - "Animations, keep-panels-mounted, minimap, snap-to-grid, compact vs classic nodes, partial selection, tree view, arc rendering style, compute backend, compilation output, parameter sweeps, in-browser optimization.", + "General, Viewport, and Labs preferences: animations, keep-panels-mounted, welcome guide, minimap, snap-to-grid, compact nodes, partial selection, arc rendering style, notebook, net components, and compilation output.", "compilation-output": "The Compilation bottom-panel tab: enabling it, the GPU verdict line, structural blockers, shader emission failures, per-item GPU/CPU/untested/no-HIR/unused status, and HIR node counts.", examples: diff --git a/libs/@hashintel/petrinaut-core/src/constraint/constraint.ts b/libs/@hashintel/petrinaut-core/src/constraint/constraint.ts index 357ed17710a..3e4f9407631 100644 --- a/libs/@hashintel/petrinaut-core/src/constraint/constraint.ts +++ b/libs/@hashintel/petrinaut-core/src/constraint/constraint.ts @@ -98,7 +98,7 @@ export const stateConstraintSchema = z .meta({ id: "StateConstraint", description: - "One boolean condition over the simulation state, authored like a metric body but returning boolean, e.g. `return state.places.Queue.count <= 10;`. Observed while a run goes.", + "One boolean condition over the simulation state, e.g. `state.places.Queue.count <= 10`. A function body with an explicit return is also accepted. Observed while a run goes.", }); export const constraintSchema = z diff --git a/libs/@hashintel/petrinaut-core/src/constraint/indicator-metric.test.ts b/libs/@hashintel/petrinaut-core/src/constraint/indicator-metric.test.ts index 34daec16e5b..1fa922757b9 100644 --- a/libs/@hashintel/petrinaut-core/src/constraint/indicator-metric.test.ts +++ b/libs/@hashintel/petrinaut-core/src/constraint/indicator-metric.test.ts @@ -106,11 +106,12 @@ describe("wrapHirAsIndicator", () => { }); describe("compileStateConstraintIndicator", () => { - it("emits a program that reads 1 where the condition holds and 0 where it fails", () => { - const artifact = compileStateConstraintIndicator( - lowerState("return state.places.Queue.count <= 10;"), - sdcpn, - ); + it.each([ + "state.places.Queue.count <= 10", + "return state.places.Queue.count <= 10;", + "const count = state.places.Queue.count;\nreturn count <= 10;", + ])("evaluates pass and fail for %s", (code) => { + const artifact = compileStateConstraintIndicator(lowerState(code), sdcpn); expect(artifact).not.toBeNull(); expect(evaluateAt(artifact!, { Queue: 4 })).toBe(1); expect(evaluateAt(artifact!, { Queue: 10 })).toBe(1); diff --git a/libs/@hashintel/petrinaut-core/src/constraint/lower.test.ts b/libs/@hashintel/petrinaut-core/src/constraint/lower.test.ts index d470949d9ea..ca44e38f486 100644 --- a/libs/@hashintel/petrinaut-core/src/constraint/lower.test.ts +++ b/libs/@hashintel/petrinaut-core/src/constraint/lower.test.ts @@ -110,12 +110,18 @@ describe("lowerConstraint", () => { expect(result.ok).toBe(false); }); - it("lowers a boolean state condition to a state constraint", () => { + it.each([ + "state.places.Queue.count <= 10", + "\n// Keep the queue small\nstate.places.Queue.count <= 10 // limit", + "state.places.Queue.count <= 10 &&\nparameters.rate > 0", + "return state.places.Queue.count <= 10;", + "const count = state.places.Queue.count;\nreturn count <= 10;", + ])("lowers a boolean state condition: %s", (code) => { const result = lowerConstraint( { space: "state", id: "c-queue", - code: "return state.places.Queue.count <= 10;", + code, }, context, ); @@ -125,20 +131,40 @@ describe("lowerConstraint", () => { } expect(result.constraint).toMatchObject({ space: "state", + code, hir: { surface: "metric", params: [{ name: "state" }] }, }); }); - it("rejects a state condition returning a number", () => { - const result = lowerConstraint( - { space: "state", id: "c", code: "return state.places.Queue.count;" }, - context, - ); + it.each(["state.places.Queue.count", "return state.places.Queue.count;"])( + "rejects a non-boolean state condition: %s", + (code) => { + const result = lowerConstraint( + { space: "state", id: "c", code }, + context, + ); + expect(result.ok).toBe(false); + if (result.ok) { + return; + } + expect(result.diagnostics[0]?.message).toContain("boolean"); + }, + ); + + it("positions an unknown-place error on the authored expression", () => { + const code = "\nstate.places.Missing.count < 10"; + const result = lowerConstraint({ space: "state", id: "c", code }, context); expect(result.ok).toBe(false); if (result.ok) { return; } - expect(result.diagnostics[0]?.message).toContain("boolean"); + const diagnostic = result.diagnostics[0]!; + expect( + code.slice( + diagnostic.span.start, + diagnostic.span.start + diagnostic.span.length, + ), + ).toBe("Missing"); }); it("produces constraints the list schema accepts verbatim", () => { @@ -151,7 +177,7 @@ describe("lowerConstraint", () => { { space: "state", id: "b", - code: "return state.places.Queue.count > 0;", + code: "state.places.Queue.count > 0", }, context, ), diff --git a/libs/@hashintel/petrinaut-core/src/constraint/lower.ts b/libs/@hashintel/petrinaut-core/src/constraint/lower.ts index 4b1ee5f30a8..71a415cd0bc 100644 --- a/libs/@hashintel/petrinaut-core/src/constraint/lower.ts +++ b/libs/@hashintel/petrinaut-core/src/constraint/lower.ts @@ -9,7 +9,10 @@ * (`sdcpn/lowerConstraint`), Node callers lower inline. */ -import { lowerTypeScriptToHir } from "../hir/lower-typescript"; +import { + lowerStateConstraintToHir, + lowerTypeScriptToHir, +} from "../hir/lower-typescript"; import { buildMetricContext, buildScenarioExpressionContext, @@ -54,10 +57,10 @@ export function lowerConstraint( source: ConstraintSource, context: LowerConstraintContext, ): LowerConstraintResult { - const lowered = lowerTypeScriptToHir( - source.code, - CONSTRAINT_SURFACES[source.space], - ); + const lowered = + source.space === "state" + ? lowerStateConstraintToHir(source.code) + : lowerTypeScriptToHir(source.code, CONSTRAINT_SURFACES.parameters); if (!lowered.ok) { return { ok: false, diagnostics: lowered.diagnostics }; } diff --git a/libs/@hashintel/petrinaut-core/src/examples/cafe-queue.ts b/libs/@hashintel/petrinaut-core/src/examples/cafe-queue.ts index 2f2e1cfd1e6..bbd2fd8aee1 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/cafe-queue.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/cafe-queue.ts @@ -219,12 +219,66 @@ export default TransitionKernel(() => { param__service_rate: "scenario.service_rate", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place__waiting: "12", - place__free_staff: "3", - place__serving: "0", - place__served: "0", + variables: [ + { + name: "arrival_rate", + type: "real", + expression: "1.2", + exposed: true, + optimize: null, + }, + { + name: "service_rate", + type: "real", + expression: "0.5", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__arrival_rate", + expression: "scenario.arrival_rate", + optimize: null, + }, + { + parameterId: "param__service_rate", + expression: "scenario.service_rate", + optimize: null, + }, + ], + places: { + place__waiting: { + kind: "uncoloured", + count: { + expression: "12", + optimize: null, + }, + }, + place__free_staff: { + kind: "uncoloured", + count: { + expression: "3", + optimize: null, + }, + }, + place__serving: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place__served: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + }, }, }, }, diff --git a/libs/@hashintel/petrinaut-core/src/examples/deployment-pipeline.ts b/libs/@hashintel/petrinaut-core/src/examples/deployment-pipeline.ts index cb6f1245bc3..af8def71e2a 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/deployment-pipeline.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/deployment-pipeline.ts @@ -791,8 +791,74 @@ return total === 0 ? 0 : failed / total;`, param__incident_resolution_rate: "scenario.resolution_rate", }, initialState: { - type: "per_place", - content: {}, + type: "adhoc", + content: { + variables: [ + { + name: "deployment_rate", + type: "real", + expression: "0.5", + exposed: true, + optimize: null, + }, + { + name: "incident_rate", + type: "real", + expression: "0.1", + exposed: true, + optimize: null, + }, + { + name: "risk_multiplier", + type: "real", + expression: "1", + exposed: true, + optimize: null, + }, + { + name: "mean_size", + type: "real", + expression: "1", + exposed: true, + optimize: null, + }, + { + name: "resolution_rate", + type: "real", + expression: "0.3", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__deployment_creation_rate", + expression: "scenario.deployment_rate", + optimize: null, + }, + { + parameterId: "param__incident_rate", + expression: "scenario.incident_rate", + optimize: null, + }, + { + parameterId: "param__deployment_risk_multiplier", + expression: "scenario.risk_multiplier", + optimize: null, + }, + { + parameterId: "param__mean_deployment_size", + expression: "scenario.mean_size", + optimize: null, + }, + { + parameterId: "param__incident_resolution_rate", + expression: "scenario.resolution_rate", + optimize: null, + }, + ], + places: {}, + }, }, }, { @@ -829,8 +895,62 @@ return total === 0 ? 0 : failed / total;`, param__incident_resolution_rate: "scenario.resolution_rate", }, initialState: { - type: "per_place", - content: {}, + type: "adhoc", + content: { + variables: [ + { + name: "deployment_rate", + type: "real", + expression: "0.5", + exposed: true, + optimize: null, + }, + { + name: "incident_rate", + type: "real", + expression: "0.35", + exposed: true, + optimize: null, + }, + { + name: "severity_multiplier", + type: "real", + expression: "1.25", + exposed: true, + optimize: null, + }, + { + name: "resolution_rate", + type: "real", + expression: "0.22", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__deployment_creation_rate", + expression: "scenario.deployment_rate", + optimize: null, + }, + { + parameterId: "param__incident_rate", + expression: "scenario.incident_rate", + optimize: null, + }, + { + parameterId: "param__incident_severity_multiplier", + expression: "scenario.severity_multiplier", + optimize: null, + }, + { + parameterId: "param__incident_resolution_rate", + expression: "scenario.resolution_rate", + optimize: null, + }, + ], + places: {}, + }, }, }, { @@ -867,8 +987,62 @@ return total === 0 ? 0 : failed / total;`, param__incident_rate: "scenario.incident_rate", }, initialState: { - type: "per_place", - content: {}, + type: "adhoc", + content: { + variables: [ + { + name: "deployment_rate", + type: "real", + expression: "1.2", + exposed: true, + optimize: null, + }, + { + name: "finish_rate", + type: "real", + expression: "0.45", + exposed: true, + optimize: null, + }, + { + name: "risk_multiplier", + type: "real", + expression: "1", + exposed: true, + optimize: null, + }, + { + name: "incident_rate", + type: "real", + expression: "0.08", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__deployment_creation_rate", + expression: "scenario.deployment_rate", + optimize: null, + }, + { + parameterId: "param__deployment_finish_base_rate", + expression: "scenario.finish_rate", + optimize: null, + }, + { + parameterId: "param__deployment_risk_multiplier", + expression: "scenario.risk_multiplier", + optimize: null, + }, + { + parameterId: "param__incident_rate", + expression: "scenario.incident_rate", + optimize: null, + }, + ], + places: {}, + }, }, }, { @@ -911,8 +1085,74 @@ return total === 0 ? 0 : failed / total;`, param__incident_severity_multiplier: "scenario.severity_multiplier", }, initialState: { - type: "per_place", - content: {}, + type: "adhoc", + content: { + variables: [ + { + name: "deployment_rate", + type: "real", + expression: "0.45", + exposed: true, + optimize: null, + }, + { + name: "mean_size", + type: "real", + expression: "1.8", + exposed: true, + optimize: null, + }, + { + name: "risk_multiplier", + type: "real", + expression: "1.75", + exposed: true, + optimize: null, + }, + { + name: "failure_base_rate", + type: "real", + expression: "0.07", + exposed: true, + optimize: null, + }, + { + name: "severity_multiplier", + type: "real", + expression: "1.4", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__deployment_creation_rate", + expression: "scenario.deployment_rate", + optimize: null, + }, + { + parameterId: "param__mean_deployment_size", + expression: "scenario.mean_size", + optimize: null, + }, + { + parameterId: "param__deployment_risk_multiplier", + expression: "scenario.risk_multiplier", + optimize: null, + }, + { + parameterId: "param__deployment_failure_base_rate", + expression: "scenario.failure_base_rate", + optimize: null, + }, + { + parameterId: "param__incident_severity_multiplier", + expression: "scenario.severity_multiplier", + optimize: null, + }, + ], + places: {}, + }, }, }, ], diff --git a/libs/@hashintel/petrinaut-core/src/examples/drone-patrol.ts b/libs/@hashintel/petrinaut-core/src/examples/drone-patrol.ts index 28863063467..33f47e4c12b 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/drone-patrol.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/drone-patrol.ts @@ -203,22 +203,181 @@ export default Dynamics((tokens, parameters) => { param__drain_rate: "scenario.drain_rate", }, initialState: { - type: "per_place", + type: "adhoc", content: { - // battery, altitude — one row per drone. - place__hangar: [ - [100, 0], - [100, 0], - [100, 0], - [100, 0], - [100, 0], - [100, 0], - [100, 0], - [100, 0], - [100, 0], - [100, 0], + variables: [ + { + name: "launch_rate", + type: "real", + expression: "0.6", + exposed: true, + optimize: null, + }, + { + name: "drain_rate", + type: "real", + expression: "2.5", + exposed: true, + optimize: null, + }, ], - place__airborne: [], + netParameters: [ + { + parameterId: "param__launch_rate", + expression: "scenario.launch_rate", + optimize: null, + }, + { + parameterId: "param__drain_rate", + expression: "scenario.drain_rate", + optimize: null, + }, + ], + places: { + place__hangar: { + kind: "coloured", + variables: [], + rows: [ + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + { + kind: "fixed", + cells: [ + { + expression: "100", + optimize: null, + }, + { + expression: "0", + optimize: null, + }, + ], + }, + ], + sharedColumns: {}, + }, + place__airborne: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + }, }, }, }, diff --git a/libs/@hashintel/petrinaut-core/src/examples/examples.test.ts b/libs/@hashintel/petrinaut-core/src/examples/examples.test.ts index 327ba705c0a..9382167c005 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/examples.test.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/examples.test.ts @@ -4,7 +4,9 @@ import { compileHirArtifacts } from "../hir"; import { checkSDCPN } from "../lsp/lib/checker"; import { SDCPNLanguageServer } from "../lsp/lib/create-sdcpn-language-service"; import { + cafeQueue, deploymentPipelineSDCPN, + dronePatrol, probabilisticSatellitesSDCPN, productionMachines, sirModel, @@ -14,7 +16,9 @@ import { } from "./index"; const EXAMPLES = [ + cafeQueue, deploymentPipelineSDCPN, + dronePatrol, probabilisticSatellitesSDCPN, productionMachines, sirModel, diff --git a/libs/@hashintel/petrinaut-core/src/examples/production-with-machine-failure.ts b/libs/@hashintel/petrinaut-core/src/examples/production-with-machine-failure.ts index feee304583d..95c43d1252c 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/production-with-machine-failure.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/production-with-machine-failure.ts @@ -565,14 +565,62 @@ return fleet.reduce((sum, m) => sum + m.machine_damage_ratio, 0) / fleet.length; ], parameterOverrides: {}, initialState: { - type: "code", - content: `return { - RawMaterial: scenario.raw_material, - AvailableMachines: Array.from( - { length: scenario.machines_count }, - () => ({ machine_damage_ratio: scenario.initial_machine_damage }), - ), -};`, + type: "adhoc", + content: { + variables: [ + { + name: "raw_material", + type: "integer", + expression: "10", + exposed: true, + optimize: null, + }, + { + name: "machines_count", + type: "integer", + expression: "3", + exposed: true, + optimize: null, + }, + { + name: "initial_machine_damage", + type: "ratio", + expression: "0", + exposed: true, + optimize: null, + }, + ], + netParameters: [], + places: { + "place__d662407f-c56d-4a96-bcbb-ead785a9c594": { + kind: "uncoloured", + count: { + expression: "scenario.raw_material", + optimize: null, + }, + }, + "place__2bdd959f-a5bc-404a-bd03-34fafcef66b8": { + kind: "coloured", + variables: [], + rows: [ + { + kind: "template", + count: { + expression: "scenario.machines_count", + optimize: null, + }, + cells: [ + { + expression: "scenario.initial_machine_damage", + optimize: null, + }, + ], + }, + ], + sharedColumns: {}, + }, + }, + }, }, }, ], diff --git a/libs/@hashintel/petrinaut-core/src/examples/satellites-launcher.test.ts b/libs/@hashintel/petrinaut-core/src/examples/satellites-launcher.test.ts index 9bfe5ae5292..ff0aad83f59 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/satellites-launcher.test.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/satellites-launcher.test.ts @@ -33,7 +33,13 @@ function compile( ): CompiledScenarioResult { const outcome = compileScenario( constellationScenario!, - lowerScenarioToHir(constellationScenario!), + lowerScenarioToHir(constellationScenario!, { + adHocContext: { + places: petriNetDefinition.places, + types: petriNetDefinition.types, + netParameters: petriNetDefinition.parameters, + }, + }), petriNetDefinition.parameters, petriNetDefinition.places, petriNetDefinition.types, diff --git a/libs/@hashintel/petrinaut-core/src/examples/satellites-launcher.ts b/libs/@hashintel/petrinaut-core/src/examples/satellites-launcher.ts index 5ef65b0164a..6bc288f6ad4 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/satellites-launcher.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/satellites-launcher.ts @@ -448,7 +448,62 @@ return sats.reduce((sum, s) => sum + s.velocity, 0) / sats.length;`, "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d": "scenario.satellite_initial_velocity", }, - initialState: { type: "per_place", content: {} }, + initialState: { + type: "adhoc", + content: { + variables: [ + { + name: "launch_rate", + type: "real", + expression: "0.3", + exposed: true, + optimize: null, + }, + { + name: "satellite_initial_altitude", + type: "real", + expression: "20", + exposed: true, + optimize: null, + }, + { + name: "satellite_initial_velocity", + type: "real", + expression: "11", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "0d1e2f3a-4b5c-6d7e-8f9a-0b1c2d3e4f5a", + expression: "5000", + optimize: null, + }, + { + parameterId: "6f7a8b9c-0d1e-2f3a-4b5c-6d7e8f9a0b1c", + expression: "14", + optimize: null, + }, + { + parameterId: "2f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c", + expression: "scenario.launch_rate", + optimize: null, + }, + { + parameterId: "1e2f3a4b-5c6d-7e8f-9a0b-1c2d3e4f5a6b", + expression: "scenario.satellite_initial_altitude", + optimize: null, + }, + { + parameterId: "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d", + expression: "scenario.satellite_initial_velocity", + optimize: null, + }, + ], + places: {}, + }, + }, }, { id: "scenario__earth_orbit", @@ -477,7 +532,62 @@ return sats.reduce((sum, s) => sum + s.velocity, 0) / sats.length;`, "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d": "scenario.satellite_initial_velocity", }, - initialState: { type: "per_place", content: {} }, + initialState: { + type: "adhoc", + content: { + variables: [ + { + name: "launch_rate", + type: "real", + expression: "0.5", + exposed: true, + optimize: null, + }, + { + name: "satellite_initial_altitude", + type: "real", + expression: "40", + exposed: true, + optimize: null, + }, + { + name: "satellite_initial_velocity", + type: "real", + expression: "67", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "0d1e2f3a-4b5c-6d7e-8f9a-0b1c2d3e4f5a", + expression: "400000", + optimize: null, + }, + { + parameterId: "6f7a8b9c-0d1e-2f3a-4b5c-6d7e8f9a0b1c", + expression: "50", + optimize: null, + }, + { + parameterId: "2f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c", + expression: "scenario.launch_rate", + optimize: null, + }, + { + parameterId: "1e2f3a4b-5c6d-7e8f-9a0b-1c2d3e4f5a6b", + expression: "scenario.satellite_initial_altitude", + optimize: null, + }, + { + parameterId: "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d", + expression: "scenario.satellite_initial_velocity", + optimize: null, + }, + ], + places: {}, + }, + }, }, { id: "scenario__mars_orbit", @@ -506,7 +616,62 @@ return sats.reduce((sum, s) => sum + s.velocity, 0) / sats.length;`, "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d": "scenario.satellite_initial_velocity", }, - initialState: { type: "per_place", content: {} }, + initialState: { + type: "adhoc", + content: { + variables: [ + { + name: "launch_rate", + type: "real", + expression: "0.4", + exposed: true, + optimize: null, + }, + { + name: "satellite_initial_altitude", + type: "real", + expression: "25", + exposed: true, + optimize: null, + }, + { + name: "satellite_initial_velocity", + type: "real", + expression: "29", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "0d1e2f3a-4b5c-6d7e-8f9a-0b1c2d3e4f5a", + expression: "43000", + optimize: null, + }, + { + parameterId: "6f7a8b9c-0d1e-2f3a-4b5c-6d7e8f9a0b1c", + expression: "27", + optimize: null, + }, + { + parameterId: "2f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c", + expression: "scenario.launch_rate", + optimize: null, + }, + { + parameterId: "1e2f3a4b-5c6d-7e8f-9a0b-1c2d3e4f5a6b", + expression: "scenario.satellite_initial_altitude", + optimize: null, + }, + { + parameterId: "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d", + expression: "scenario.satellite_initial_velocity", + optimize: null, + }, + ], + places: {}, + }, + }, }, { id: "scenario__solar_orbit", @@ -535,42 +700,150 @@ return sats.reduce((sum, s) => sum + s.velocity, 0) / sats.length;`, "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d": "scenario.satellite_initial_velocity", }, - initialState: { type: "per_place", content: {} }, + initialState: { + type: "adhoc", + content: { + variables: [ + { + name: "launch_rate", + type: "real", + expression: "0.6", + exposed: true, + optimize: null, + }, + { + name: "satellite_initial_altitude", + type: "real", + expression: "50", + exposed: true, + optimize: null, + }, + { + name: "satellite_initial_velocity", + type: "real", + expression: "196", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "0d1e2f3a-4b5c-6d7e-8f9a-0b1c2d3e4f5a", + expression: "5000000", + optimize: null, + }, + { + parameterId: "6f7a8b9c-0d1e-2f3a-4b5c-6d7e8f9a0b1c", + expression: "80", + optimize: null, + }, + { + parameterId: "2f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c", + expression: "scenario.launch_rate", + optimize: null, + }, + { + parameterId: "1e2f3a4b-5c6d-7e8f-9a0b-1c2d3e4f5a6b", + expression: "scenario.satellite_initial_altitude", + optimize: null, + }, + { + parameterId: "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d", + expression: "scenario.satellite_initial_velocity", + optimize: null, + }, + ], + places: {}, + }, + }, }, { id: "scenario__pre_deployed_constellation", name: "Pre-deployed Constellation", description: - "Starts with a configurable number of satellites already in orbit, evenly spaced in a ring around the planet. The initial state is defined as code from the scenario parameters.", + "Starts with a configurable number of satellites already in orbit, evenly spaced in a ring around the planet. A dynamic row in the ad-hoc scenario form generates the initial state from the scenario parameters.", scenarioParameters: [ { type: "integer", identifier: "number_of_satellites", default: 8 }, { type: "real", identifier: "initial_altitude", default: 40 }, ], parameterOverrides: {}, initialState: { - type: "code", - content: `const distanceToCenter = - parameters.planet_radius + scenario.initial_altitude; - -// Speed of a circular orbit at this radius. The orbit dynamics divide by -// velocity, so a satellite must start moving — a stationary token would make -// the direction derivative 0/0 on the very first step. -const orbitalSpeed = Math.sqrt( - parameters.gravitational_constant / distanceToCenter, -); - -return { - Space: range(scenario.number_of_satellites).map((i) => { - const angle = Math.PI * 2 * (i / scenario.number_of_satellites); - return { - x: Math.cos(angle) * distanceToCenter, - y: Math.sin(angle) * distanceToCenter, - // Tangential heading: a quarter turn ahead of the outward radius. - direction: angle + Math.PI / 2, - velocity: orbitalSpeed, - }; - }), -};`, + type: "adhoc", + content: { + variables: [ + { + name: "number_of_satellites", + type: "integer", + expression: "8", + exposed: true, + optimize: null, + }, + { + name: "initial_altitude", + type: "real", + expression: "40", + exposed: true, + optimize: null, + }, + ], + netParameters: [], + places: { + "3cbc7944-34cb-4eeb-b779-4e392a171fe1": { + kind: "coloured", + variables: [ + { + name: "distanceToCenter", + type: "real", + expression: + "parameters.planet_radius + scenario.initial_altitude", + optimize: null, + }, + { + name: "orbitalSpeed", + type: "real", + expression: + "Math.sqrt(\n parameters.gravitational_constant / distanceToCenter,\n)", + optimize: null, + }, + { + name: "angle", + type: "real", + expression: + "Math.PI * 2 * (i / scenario.number_of_satellites)", + optimize: null, + }, + ], + rows: [ + { + kind: "template", + count: { + expression: "scenario.number_of_satellites", + optimize: null, + }, + cells: [ + { + expression: "Math.cos(angle) * distanceToCenter", + optimize: null, + }, + { + expression: "Math.sin(angle) * distanceToCenter", + optimize: null, + }, + { + expression: "angle + Math.PI / 2", + optimize: null, + }, + { + expression: "orbitalSpeed", + optimize: null, + }, + ], + }, + ], + sharedColumns: {}, + }, + }, + }, }, }, ], diff --git a/libs/@hashintel/petrinaut-core/src/examples/sir-model.ts b/libs/@hashintel/petrinaut-core/src/examples/sir-model.ts index 386557bb519..71d6ac0ad0b 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/sir-model.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/sir-model.ts @@ -159,12 +159,60 @@ return { param__recovery_rate: "0.8", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place__susceptible: - "scenario.population * (1 - scenario.infected_ratio)", - place__infected: "scenario.population * scenario.infected_ratio", - place__recovered: "0", + variables: [ + { + name: "population", + type: "integer", + expression: "1000", + exposed: true, + optimize: null, + }, + { + name: "infected_ratio", + type: "ratio", + expression: "0.01", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__infection_rate", + expression: "1.5", + optimize: null, + }, + { + parameterId: "param__recovery_rate", + expression: "0.8", + optimize: null, + }, + ], + places: { + place__susceptible: { + kind: "uncoloured", + count: { + expression: + "scenario.population * (1 - scenario.infected_ratio)", + optimize: null, + }, + }, + place__infected: { + kind: "uncoloured", + count: { + expression: "scenario.population * scenario.infected_ratio", + optimize: null, + }, + }, + place__recovered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + }, }, }, }, @@ -182,12 +230,60 @@ return { param__recovery_rate: "0.5", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place__susceptible: - "scenario.population * (1 - scenario.infected_ratio)", - place__infected: "scenario.population * scenario.infected_ratio", - place__recovered: "0", + variables: [ + { + name: "population", + type: "integer", + expression: "10000", + exposed: true, + optimize: null, + }, + { + name: "infected_ratio", + type: "ratio", + expression: "0.0001", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__infection_rate", + expression: "6", + optimize: null, + }, + { + parameterId: "param__recovery_rate", + expression: "0.5", + optimize: null, + }, + ], + places: { + place__susceptible: { + kind: "uncoloured", + count: { + expression: + "scenario.population * (1 - scenario.infected_ratio)", + optimize: null, + }, + }, + place__infected: { + kind: "uncoloured", + count: { + expression: "scenario.population * scenario.infected_ratio", + optimize: null, + }, + }, + place__recovered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + }, }, }, }, @@ -205,12 +301,60 @@ return { param__recovery_rate: "1.2", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place__susceptible: - "scenario.population * (1 - scenario.infected_ratio)", - place__infected: "scenario.population * scenario.infected_ratio", - place__recovered: "0", + variables: [ + { + name: "population", + type: "integer", + expression: "1000", + exposed: true, + optimize: null, + }, + { + name: "infected_ratio", + type: "ratio", + expression: "0.05", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__infection_rate", + expression: "0.6", + optimize: null, + }, + { + parameterId: "param__recovery_rate", + expression: "1.2", + optimize: null, + }, + ], + places: { + place__susceptible: { + kind: "uncoloured", + count: { + expression: + "scenario.population * (1 - scenario.infected_ratio)", + optimize: null, + }, + }, + place__infected: { + kind: "uncoloured", + count: { + expression: "scenario.population * scenario.infected_ratio", + optimize: null, + }, + }, + place__recovered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + }, }, }, }, @@ -228,12 +372,60 @@ return { param__recovery_rate: "0.5", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place__susceptible: - "scenario.population * (1 - scenario.infected_ratio)", - place__infected: "scenario.population * scenario.infected_ratio", - place__recovered: "0", + variables: [ + { + name: "population", + type: "integer", + expression: "100000", + exposed: true, + optimize: null, + }, + { + name: "infected_ratio", + type: "ratio", + expression: "0.00005", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__infection_rate", + expression: "2.5", + optimize: null, + }, + { + parameterId: "param__recovery_rate", + expression: "0.5", + optimize: null, + }, + ], + places: { + place__susceptible: { + kind: "uncoloured", + count: { + expression: + "scenario.population * (1 - scenario.infected_ratio)", + optimize: null, + }, + }, + place__infected: { + kind: "uncoloured", + count: { + expression: "scenario.population * scenario.infected_ratio", + optimize: null, + }, + }, + place__recovered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + }, }, }, }, diff --git a/libs/@hashintel/petrinaut-core/src/examples/supply-chain-profit.ts b/libs/@hashintel/petrinaut-core/src/examples/supply-chain-profit.ts index 22c8ab9eaee..746272b23d6 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/supply-chain-profit.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/supply-chain-profit.ts @@ -383,13 +383,133 @@ return revenue - variableCost - productionCapacityCost - marketingCost - expedit param_demand_multiplier: "scenario.demand_multiplier", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place_raw_inventory: "200", - place_finished_goods: "100", - place_customer_demand: "0", - place_sold_orders: "0", - place_lost_sales: "0", + variables: [ + { + name: "production_rate", + type: "real", + expression: "125", + exposed: true, + optimize: null, + }, + { + name: "replenishment_aggressiveness", + type: "integer", + expression: "190", + exposed: true, + optimize: null, + }, + { + name: "batch_size", + type: "integer", + expression: "220", + exposed: true, + optimize: null, + }, + { + name: "selling_price", + type: "real", + expression: "37", + exposed: true, + optimize: null, + }, + { + name: "expedite_fraction", + type: "ratio", + expression: "0.33", + exposed: true, + optimize: null, + }, + { + name: "marketing_spend", + type: "real", + expression: "32", + exposed: true, + optimize: null, + }, + { + name: "demand_multiplier", + type: "real", + expression: "1", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param_production_rate", + expression: "scenario.production_rate", + optimize: null, + }, + { + parameterId: "param_replenishment_aggressiveness", + expression: "scenario.replenishment_aggressiveness", + optimize: null, + }, + { + parameterId: "param_batch_size", + expression: "scenario.batch_size", + optimize: null, + }, + { + parameterId: "param_selling_price", + expression: "scenario.selling_price", + optimize: null, + }, + { + parameterId: "param_expedite_fraction", + expression: "scenario.expedite_fraction", + optimize: null, + }, + { + parameterId: "param_marketing_spend", + expression: "scenario.marketing_spend", + optimize: null, + }, + { + parameterId: "param_demand_multiplier", + expression: "scenario.demand_multiplier", + optimize: null, + }, + ], + places: { + place_raw_inventory: { + kind: "uncoloured", + count: { + expression: "200", + optimize: null, + }, + }, + place_finished_goods: { + kind: "uncoloured", + count: { + expression: "100", + optimize: null, + }, + }, + place_customer_demand: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_sold_orders: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_lost_sales: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + }, }, }, }, @@ -421,13 +541,133 @@ return revenue - variableCost - productionCapacityCost - marketingCost - expedit param_demand_multiplier: "scenario.demand_multiplier", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place_raw_inventory: "0", - place_finished_goods: "0", - place_customer_demand: "0", - place_sold_orders: "0", - place_lost_sales: "0", + variables: [ + { + name: "production_rate", + type: "real", + expression: "300", + exposed: true, + optimize: null, + }, + { + name: "replenishment_aggressiveness", + type: "integer", + expression: "160", + exposed: true, + optimize: null, + }, + { + name: "batch_size", + type: "integer", + expression: "600", + exposed: true, + optimize: null, + }, + { + name: "selling_price", + type: "real", + expression: "37", + exposed: true, + optimize: null, + }, + { + name: "expedite_fraction", + type: "ratio", + expression: "0.33", + exposed: true, + optimize: null, + }, + { + name: "marketing_spend", + type: "real", + expression: "32", + exposed: true, + optimize: null, + }, + { + name: "demand_multiplier", + type: "real", + expression: "1", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param_production_rate", + expression: "scenario.production_rate", + optimize: null, + }, + { + parameterId: "param_replenishment_aggressiveness", + expression: "scenario.replenishment_aggressiveness", + optimize: null, + }, + { + parameterId: "param_batch_size", + expression: "scenario.batch_size", + optimize: null, + }, + { + parameterId: "param_selling_price", + expression: "scenario.selling_price", + optimize: null, + }, + { + parameterId: "param_expedite_fraction", + expression: "scenario.expedite_fraction", + optimize: null, + }, + { + parameterId: "param_marketing_spend", + expression: "scenario.marketing_spend", + optimize: null, + }, + { + parameterId: "param_demand_multiplier", + expression: "scenario.demand_multiplier", + optimize: null, + }, + ], + places: { + place_raw_inventory: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_finished_goods: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_customer_demand: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_sold_orders: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_lost_sales: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + }, }, }, }, diff --git a/libs/@hashintel/petrinaut-core/src/examples/supply-chain-with-disruption.ts b/libs/@hashintel/petrinaut-core/src/examples/supply-chain-with-disruption.ts index 0ce27979690..9e90237f023 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/supply-chain-with-disruption.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/supply-chain-with-disruption.ts @@ -1693,26 +1693,203 @@ return orders.reduce((sum, o) => sum + o.age, 0) / orders.length;`, param_initial_finished_goods: "scenario.initial_finished_goods", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place_supplier_a_available: "1", - place_supplier_a_down: "0", - place_supplier_b_available: "1", - place_supplier_b_down: "0", - place_raw_materials: "scenario.initial_raw_materials", - place_finished_goods: "scenario.initial_finished_goods", - place_damaged_inbound: "0", - place_scrap: "0", - place_delivered: "0", - place_lost: "0", - place_cancelled: "0", - place_machine_up: [[0.95, 0.08]], - place_machine_down: [], - place_inbound: [], - place_wip: [], - place_orders: [], - place_backorders: [], - place_outbound: [], + variables: [ + { + name: "demand_multiplier", + type: "real", + expression: "1", + exposed: true, + optimize: null, + }, + { + name: "a_share_bias", + type: "ratio", + expression: "1", + exposed: true, + optimize: null, + }, + { + name: "initial_raw_materials", + type: "integer", + expression: "8", + exposed: true, + optimize: null, + }, + { + name: "initial_finished_goods", + type: "integer", + expression: "12", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param_demand_rate", + expression: + "parameters.demand_rate * scenario.demand_multiplier", + optimize: null, + }, + { + parameterId: "param_supplier_a_order_rate", + expression: + "parameters.supplier_a_order_rate * scenario.a_share_bias", + optimize: null, + }, + { + parameterId: "param_supplier_b_order_rate", + expression: + "parameters.supplier_b_order_rate * (2 - scenario.a_share_bias)", + optimize: null, + }, + { + parameterId: "param_initial_raw_materials", + expression: "scenario.initial_raw_materials", + optimize: null, + }, + { + parameterId: "param_initial_finished_goods", + expression: "scenario.initial_finished_goods", + optimize: null, + }, + ], + places: { + place_supplier_a_available: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_a_down: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_supplier_b_available: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_b_down: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_raw_materials: { + kind: "uncoloured", + count: { + expression: "scenario.initial_raw_materials", + optimize: null, + }, + }, + place_damaged_inbound: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_finished_goods: { + kind: "uncoloured", + count: { + expression: "scenario.initial_finished_goods", + optimize: null, + }, + }, + place_scrap: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_delivered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_lost: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_cancelled: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_inbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_wip: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_orders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_backorders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_outbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_machine_up: { + kind: "coloured", + variables: [], + rows: [ + { + kind: "fixed", + cells: [ + { + expression: "0.95", + optimize: null, + }, + { + expression: "0.08", + optimize: null, + }, + ], + }, + ], + sharedColumns: {}, + }, + place_machine_down: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + }, }, }, }, @@ -1754,26 +1931,203 @@ return orders.reduce((sum, o) => sum + o.age, 0) / orders.length;`, param_initial_finished_goods: "scenario.initial_finished_goods", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place_supplier_a_available: "1", - place_supplier_a_down: "0", - place_supplier_b_available: "1", - place_supplier_b_down: "0", - place_raw_materials: "6", - place_finished_goods: "scenario.initial_finished_goods", - place_damaged_inbound: "0", - place_scrap: "0", - place_delivered: "0", - place_lost: "0", - place_cancelled: "0", - place_machine_up: [[0.9, 0.12]], - place_machine_down: [], - place_inbound: [], - place_wip: [], - place_orders: [], - place_backorders: [], - place_outbound: [], + variables: [ + { + name: "demand_multiplier", + type: "real", + expression: "1.8", + exposed: true, + optimize: null, + }, + { + name: "lead_time_multiplier", + type: "real", + expression: "1.7", + exposed: true, + optimize: null, + }, + { + name: "damage_threshold", + type: "real", + expression: "0.78", + exposed: true, + optimize: null, + }, + { + name: "initial_finished_goods", + type: "integer", + expression: "18", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param_demand_rate", + expression: + "parameters.demand_rate * scenario.demand_multiplier", + optimize: null, + }, + { + parameterId: "param_supplier_a_lead_time", + expression: + "parameters.supplier_a_lead_time * scenario.lead_time_multiplier", + optimize: null, + }, + { + parameterId: "param_supplier_b_lead_time", + expression: + "parameters.supplier_b_lead_time * scenario.lead_time_multiplier", + optimize: null, + }, + { + parameterId: "param_inbound_damage_threshold", + expression: "scenario.damage_threshold", + optimize: null, + }, + { + parameterId: "param_initial_finished_goods", + expression: "scenario.initial_finished_goods", + optimize: null, + }, + ], + places: { + place_supplier_a_available: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_a_down: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_supplier_b_available: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_b_down: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_raw_materials: { + kind: "uncoloured", + count: { + expression: "6", + optimize: null, + }, + }, + place_damaged_inbound: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_finished_goods: { + kind: "uncoloured", + count: { + expression: "scenario.initial_finished_goods", + optimize: null, + }, + }, + place_scrap: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_delivered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_lost: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_cancelled: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_inbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_wip: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_orders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_backorders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_outbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_machine_up: { + kind: "coloured", + variables: [], + rows: [ + { + kind: "fixed", + cells: [ + { + expression: "0.9", + optimize: null, + }, + { + expression: "0.12", + optimize: null, + }, + ], + }, + ], + sharedColumns: {}, + }, + place_machine_down: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + }, }, }, }, @@ -1806,26 +2160,184 @@ return orders.reduce((sum, o) => sum + o.age, 0) / orders.length;`, param_initial_finished_goods: "scenario.initial_finished_goods", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place_supplier_a_available: "0", - place_supplier_a_down: "1", - place_supplier_b_available: "1", - place_supplier_b_down: "0", - place_raw_materials: "8", - place_finished_goods: "scenario.initial_finished_goods", - place_damaged_inbound: "0", - place_scrap: "0", - place_delivered: "0", - place_lost: "0", - place_cancelled: "0", - place_machine_up: [[0.94, 0.1]], - place_machine_down: [], - place_inbound: [], - place_wip: [], - place_orders: [], - place_backorders: [], - place_outbound: [], + variables: [ + { + name: "a_recovery_rate", + type: "real", + expression: "0.08", + exposed: true, + optimize: null, + }, + { + name: "b_expedite_multiplier", + type: "real", + expression: "1.6", + exposed: true, + optimize: null, + }, + { + name: "initial_finished_goods", + type: "integer", + expression: "10", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param_supplier_a_repair_rate", + expression: "scenario.a_recovery_rate", + optimize: null, + }, + { + parameterId: "param_supplier_b_order_rate", + expression: + "parameters.supplier_b_order_rate * scenario.b_expedite_multiplier", + optimize: null, + }, + { + parameterId: "param_initial_finished_goods", + expression: "scenario.initial_finished_goods", + optimize: null, + }, + ], + places: { + place_supplier_a_available: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_supplier_a_down: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_b_available: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_b_down: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_raw_materials: { + kind: "uncoloured", + count: { + expression: "8", + optimize: null, + }, + }, + place_damaged_inbound: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_finished_goods: { + kind: "uncoloured", + count: { + expression: "scenario.initial_finished_goods", + optimize: null, + }, + }, + place_scrap: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_delivered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_lost: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_cancelled: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_inbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_wip: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_orders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_backorders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_outbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_machine_up: { + kind: "coloured", + variables: [], + rows: [ + { + kind: "fixed", + cells: [ + { + expression: "0.94", + optimize: null, + }, + { + expression: "0.1", + optimize: null, + }, + ], + }, + ], + sharedColumns: {}, + }, + place_machine_down: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + }, }, }, }, @@ -1865,26 +2377,197 @@ return orders.reduce((sum, o) => sum + o.age, 0) / orders.length;`, param_initial_raw_materials: "scenario.initial_raw_materials", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place_supplier_a_available: "1", - place_supplier_a_down: "0", - place_supplier_b_available: "1", - place_supplier_b_down: "0", - place_raw_materials: "scenario.initial_raw_materials", - place_finished_goods: "8", - place_damaged_inbound: "0", - place_scrap: "0", - place_delivered: "0", - place_lost: "0", - place_cancelled: "0", - place_machine_up: [[0.96, 0.06]], - place_machine_down: [], - place_inbound: [], - place_wip: [], - place_orders: [], - place_backorders: [], - place_outbound: [], + variables: [ + { + name: "a_order_multiplier", + type: "real", + expression: "0.35", + exposed: true, + optimize: null, + }, + { + name: "b_order_multiplier", + type: "real", + expression: "1.9", + exposed: true, + optimize: null, + }, + { + name: "b_risk_multiplier", + type: "real", + expression: "1.45", + exposed: true, + optimize: null, + }, + { + name: "initial_raw_materials", + type: "integer", + expression: "12", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param_supplier_a_order_rate", + expression: + "parameters.supplier_a_order_rate * scenario.a_order_multiplier", + optimize: null, + }, + { + parameterId: "param_supplier_b_order_rate", + expression: + "parameters.supplier_b_order_rate * scenario.b_order_multiplier", + optimize: null, + }, + { + parameterId: "param_supplier_b_risk_multiplier", + expression: "scenario.b_risk_multiplier", + optimize: null, + }, + { + parameterId: "param_initial_raw_materials", + expression: "scenario.initial_raw_materials", + optimize: null, + }, + ], + places: { + place_supplier_a_available: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_a_down: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_supplier_b_available: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_b_down: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_raw_materials: { + kind: "uncoloured", + count: { + expression: "scenario.initial_raw_materials", + optimize: null, + }, + }, + place_damaged_inbound: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_finished_goods: { + kind: "uncoloured", + count: { + expression: "8", + optimize: null, + }, + }, + place_scrap: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_delivered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_lost: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_cancelled: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_inbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_wip: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_orders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_backorders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_outbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_machine_up: { + kind: "coloured", + variables: [], + rows: [ + { + kind: "fixed", + cells: [ + { + expression: "0.96", + optimize: null, + }, + { + expression: "0.06", + optimize: null, + }, + ], + }, + ], + sharedColumns: {}, + }, + place_machine_down: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + }, }, }, }, @@ -1926,26 +2609,203 @@ return orders.reduce((sum, o) => sum + o.age, 0) / orders.length;`, param_initial_finished_goods: "scenario.initial_finished_goods", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place_supplier_a_available: "1", - place_supplier_a_down: "0", - place_supplier_b_available: "1", - place_supplier_b_down: "0", - place_raw_materials: "scenario.initial_raw_materials", - place_finished_goods: "scenario.initial_finished_goods", - place_damaged_inbound: "0", - place_scrap: "0", - place_delivered: "0", - place_lost: "0", - place_cancelled: "0", - place_machine_up: [[1, 0.03]], - place_machine_down: [], - place_inbound: [], - place_wip: [], - place_orders: [], - place_backorders: [], - place_outbound: [], + variables: [ + { + name: "initial_raw_materials", + type: "integer", + expression: "18", + exposed: true, + optimize: null, + }, + { + name: "initial_finished_goods", + type: "integer", + expression: "24", + exposed: true, + optimize: null, + }, + { + name: "maintenance_multiplier", + type: "real", + expression: "2.2", + exposed: true, + optimize: null, + }, + { + name: "supplier_recovery_multiplier", + type: "real", + expression: "1.8", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param_maintenance_rate", + expression: + "parameters.maintenance_rate * scenario.maintenance_multiplier", + optimize: null, + }, + { + parameterId: "param_supplier_a_repair_rate", + expression: + "parameters.supplier_a_repair_rate * scenario.supplier_recovery_multiplier", + optimize: null, + }, + { + parameterId: "param_supplier_b_repair_rate", + expression: + "parameters.supplier_b_repair_rate * scenario.supplier_recovery_multiplier", + optimize: null, + }, + { + parameterId: "param_initial_raw_materials", + expression: "scenario.initial_raw_materials", + optimize: null, + }, + { + parameterId: "param_initial_finished_goods", + expression: "scenario.initial_finished_goods", + optimize: null, + }, + ], + places: { + place_supplier_a_available: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_a_down: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_supplier_b_available: { + kind: "uncoloured", + count: { + expression: "1", + optimize: null, + }, + }, + place_supplier_b_down: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_raw_materials: { + kind: "uncoloured", + count: { + expression: "scenario.initial_raw_materials", + optimize: null, + }, + }, + place_damaged_inbound: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_finished_goods: { + kind: "uncoloured", + count: { + expression: "scenario.initial_finished_goods", + optimize: null, + }, + }, + place_scrap: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_delivered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_lost: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_cancelled: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place_inbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_wip: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_orders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_backorders: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_outbound: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + place_machine_up: { + kind: "coloured", + variables: [], + rows: [ + { + kind: "fixed", + cells: [ + { + expression: "1", + optimize: null, + }, + { + expression: "0.03", + optimize: null, + }, + ], + }, + ], + sharedColumns: {}, + }, + place_machine_down: { + kind: "coloured", + variables: [], + rows: [], + sharedColumns: {}, + }, + }, }, }, }, diff --git a/libs/@hashintel/petrinaut-core/src/examples/vaccination-campaign.test.ts b/libs/@hashintel/petrinaut-core/src/examples/vaccination-campaign.test.ts index e8b20267ee1..472edb267a9 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/vaccination-campaign.test.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/vaccination-campaign.test.ts @@ -29,7 +29,13 @@ const infectedPlace = petriNetDefinition.places.find( const { artifacts } = compileHirArtifacts(petriNetDefinition, undefined, { includeHir: true, }); -const winterWaveHir = lowerScenarioToHir(winterWave); +const winterWaveHir = lowerScenarioToHir(winterWave, { + adHocContext: { + places: petriNetDefinition.places, + types: petriNetDefinition.types, + netParameters: petriNetDefinition.parameters, + }, +}); /** The two levers the optimization stories range over. */ type Levers = { vaccination_coverage: number; contact_reduction: number }; diff --git a/libs/@hashintel/petrinaut-core/src/examples/vaccination-campaign.ts b/libs/@hashintel/petrinaut-core/src/examples/vaccination-campaign.ts index 6978ee0fbe3..019cb268a44 100644 --- a/libs/@hashintel/petrinaut-core/src/examples/vaccination-campaign.ts +++ b/libs/@hashintel/petrinaut-core/src/examples/vaccination-campaign.ts @@ -213,14 +213,82 @@ return { param__contact_reduction: "scenario.contact_reduction", }, initialState: { - type: "per_place", + type: "adhoc", content: { - place__susceptible: - "scenario.population - scenario.initial_infected - Math.round((scenario.population - scenario.initial_infected) * scenario.vaccination_coverage)", - place__infected: "scenario.initial_infected", - place__recovered: "0", - place__vaccinated: - "Math.round((scenario.population - scenario.initial_infected) * scenario.vaccination_coverage)", + variables: [ + { + name: "population", + type: "integer", + expression: "1000", + exposed: true, + optimize: null, + }, + { + name: "initial_infected", + type: "integer", + expression: "20", + exposed: true, + optimize: null, + }, + { + name: "vaccination_coverage", + type: "ratio", + expression: "0.3", + exposed: true, + optimize: null, + }, + { + name: "contact_reduction", + type: "ratio", + expression: "0.2", + exposed: true, + optimize: null, + }, + ], + netParameters: [ + { + parameterId: "param__vaccination_coverage", + expression: "scenario.vaccination_coverage", + optimize: null, + }, + { + parameterId: "param__contact_reduction", + expression: "scenario.contact_reduction", + optimize: null, + }, + ], + places: { + place__susceptible: { + kind: "uncoloured", + count: { + expression: + "scenario.population - scenario.initial_infected - Math.round((scenario.population - scenario.initial_infected) * scenario.vaccination_coverage)", + optimize: null, + }, + }, + place__infected: { + kind: "uncoloured", + count: { + expression: "scenario.initial_infected", + optimize: null, + }, + }, + place__recovered: { + kind: "uncoloured", + count: { + expression: "0", + optimize: null, + }, + }, + place__vaccinated: { + kind: "uncoloured", + count: { + expression: + "Math.round((scenario.population - scenario.initial_infected) * scenario.vaccination_coverage)", + optimize: null, + }, + }, + }, }, }, }, diff --git a/libs/@hashintel/petrinaut-core/src/hir.ts b/libs/@hashintel/petrinaut-core/src/hir.ts index 78a594cdbfa..b72c6c74b12 100644 --- a/libs/@hashintel/petrinaut-core/src/hir.ts +++ b/libs/@hashintel/petrinaut-core/src/hir.ts @@ -92,11 +92,13 @@ export { spanSchema, } from "./hir/hir-schema"; export { + lowerStateConstraintToHir, lowerTypeScriptToHir, type LowerTypeScriptResult, } from "./hir/lower-typescript"; export { AMBIENT_INPUT_NAMES, + detectStateConstraintForm, detectUserCodeForm, type DualFormSurfaceKind, type UserCodeForm, diff --git a/libs/@hashintel/petrinaut-core/src/hir/lint.ts b/libs/@hashintel/petrinaut-core/src/hir/lint.ts index da6176e12c8..af0dfaac73a 100644 --- a/libs/@hashintel/petrinaut-core/src/hir/lint.ts +++ b/libs/@hashintel/petrinaut-core/src/hir/lint.ts @@ -18,7 +18,10 @@ import { emitBufferMetricJs, } from "./emit-buffer-js"; import { walkHir } from "./hir"; -import { lowerTypeScriptToHir } from "./lower-typescript"; +import { + lowerStateConstraintToHir, + lowerTypeScriptToHir, +} from "./lower-typescript"; import { typecheckHir } from "./typecheck"; import type { HirAnalysis } from "./analyze"; @@ -110,7 +113,10 @@ export function lintHirUserCode( options: HirLintOptions = {}, ): HirLintResult { const subsetSeverity = options.subsetSeverity ?? "error"; - const lowered = lowerTypeScriptToHir(code, context.surface); + const lowered = + context.surface === "metric" && context.expected === "boolean" + ? lowerStateConstraintToHir(code) + : lowerTypeScriptToHir(code, context.surface); if (!lowered.ok) { return { diff --git a/libs/@hashintel/petrinaut-core/src/hir/lower-typescript.ts b/libs/@hashintel/petrinaut-core/src/hir/lower-typescript.ts index 5374d5193d9..c2eb64ea37d 100644 --- a/libs/@hashintel/petrinaut-core/src/hir/lower-typescript.ts +++ b/libs/@hashintel/petrinaut-core/src/hir/lower-typescript.ts @@ -25,6 +25,7 @@ import ts from "typescript"; import { HIR_MATH_FNS, HIR_STRING_FNS, walkHir } from "./hir"; import { AMBIENT_INPUT_NAMES, + detectStateConstraintForm, detectUserCodeForm, type DualFormSurfaceKind, } from "./user-code-form"; @@ -1872,6 +1873,20 @@ function bareBodyPrefix(surface: DualFormSurfaceKind): string { const BARE_BODY_SUFFIX = "\n}"; +/** Lowers either state-constraint form with spans relative to the authored source. */ +export const lowerStateConstraintToHir = ( + code: string, +): LowerTypeScriptResult => { + const expression = detectStateConstraintForm(code) === "expression"; + return lowerWrappedBodyToHir( + code, + "metric", + expression ? `${METRIC_PREFIX}return (\n` : METRIC_PREFIX, + expression ? `\n);${METRIC_SUFFIX}` : METRIC_SUFFIX, + (lowering) => lowering.lowerMetricModule(), + ); +}; + /** * Lowers user-authored TypeScript to an `HirFunction`. * diff --git a/libs/@hashintel/petrinaut-core/src/hir/user-code-form.test.ts b/libs/@hashintel/petrinaut-core/src/hir/user-code-form.test.ts index a09867268a0..6db6f4df177 100644 --- a/libs/@hashintel/petrinaut-core/src/hir/user-code-form.test.ts +++ b/libs/@hashintel/petrinaut-core/src/hir/user-code-form.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from "vitest"; -import { detectUserCodeForm } from "./user-code-form"; +import { + detectStateConstraintForm, + detectUserCodeForm, +} from "./user-code-form"; describe("detectUserCodeForm", () => { it.each([ @@ -28,3 +31,24 @@ describe("detectUserCodeForm", () => { expect(detectUserCodeForm(code)).toBe(expected); }); }); + +describe("detectStateConstraintForm", () => { + it.each([ + ["state.places.Queue.count <= 10", "expression"], + ["state.places.", "expression"], + [ + "// return is supplied internally\nstate.places.Queue.count > 0", + "expression", + ], + ['state.places["return"].count > 0', "expression"], + [ + "state.places.Queue.tokens.map((token) => { return token; }).length > 0", + "expression", + ], + ["return true;", "body"], + ["const count = state.places.Queue.count;\nreturn count > 0;", "body"], + ["if (state.places.Queue.count > 0) return true;\nreturn false;", "body"], + ] as const)("classifies %j as %s", (code, expected) => { + expect(detectStateConstraintForm(code)).toBe(expected); + }); +}); diff --git a/libs/@hashintel/petrinaut-core/src/hir/user-code-form.ts b/libs/@hashintel/petrinaut-core/src/hir/user-code-form.ts index 01e46c636f2..d5f4eb46271 100644 --- a/libs/@hashintel/petrinaut-core/src/hir/user-code-form.ts +++ b/libs/@hashintel/petrinaut-core/src/hir/user-code-form.ts @@ -68,3 +68,19 @@ export function detectUserCodeForm(code: string): UserCodeForm { ); return sourceFile.statements.some(isExportStatement) ? "module" : "body"; } + +/** State constraints accept expressions and existing bodies with explicit returns. */ +export const detectStateConstraintForm = ( + code: string, +): "expression" | "body" => { + const sourceFile = ts.createSourceFile( + "constraint-form.ts", + code, + ts.ScriptTarget.ES2020, + ); + return sourceFile.statements.some( + (statement) => !ts.isExpressionStatement(statement), + ) + ? "body" + : "expression"; +}; diff --git a/libs/@hashintel/petrinaut-core/src/lsp/lib/constraint-session.test.ts b/libs/@hashintel/petrinaut-core/src/lsp/lib/constraint-session.test.ts index 92d06d66cd1..14417a37c97 100644 --- a/libs/@hashintel/petrinaut-core/src/lsp/lib/constraint-session.test.ts +++ b/libs/@hashintel/petrinaut-core/src/lsp/lib/constraint-session.test.ts @@ -106,6 +106,20 @@ describe("constraint session virtual files", () => { expect(code.suffix).toBe("\n}"); }); + it("supplies a return outside the authored state expression", () => { + const expression = "state.places.P.count > 3"; + const files = generateConstraintSessionFiles( + SDCPN, + session("state", expression), + ); + const code = files.get(CODE_PATH)!; + expect(code.prefix).toContain( + "function __constraint(state: MetricState): boolean {\nreturn (\n", + ); + expect(code.content).toBe(expression); + expect(code.suffix).toBe("\n);\n}"); + }); + it("declares `scenario` as an empty record when the study has no parameters", () => { const files = generateConstraintSessionFiles(SDCPN, { ...session("parameters", "true"), @@ -150,16 +164,33 @@ describe("constraint session diagnostics", () => { ]); }); - it("accepts a boolean state-space body", () => { - expect( - constraintDiagnostics("state", 'return state.places["P"].count > 3;'), - ).toEqual([]); + it.each([ + "state.places.P.count > 3", + "// return is implicit\nstate.places.P.count > 3 &&\nparameters.weight > 0 // weight", + 'return state.places["P"].count > 3;', + "const count = state.places.P.count;\nreturn count > 3;", + ])("accepts a boolean state condition: %s", (code) => { + expect(constraintDiagnostics("state", code)).toEqual([]); }); - it("rejects a state-space body returning a number", () => { - expect( - constraintDiagnostics("state", 'return state.places["P"].count;'), - ).toEqual(["Type 'number' is not assignable to type 'boolean'."]); + it.each(["state.places.P.count", 'return state.places["P"].count;'])( + "rejects a non-boolean state condition: %s", + (code) => { + expect(constraintDiagnostics("state", code)).toEqual([ + "Type 'number' is not assignable to type 'boolean'.", + ]); + }, + ); + + it("positions TypeScript errors on the authored expression", () => { + const code = "\nstate.places.Missing.count > 0"; + const server = makeServer(session("state", code)); + const diagnostics = server.getSemanticDiagnostics(CODE_PATH); + expect(diagnostics).toHaveLength(1); + expect(diagnostics[0]).toMatchObject({ + start: code.indexOf("Missing"), + length: "Missing".length, + }); }); it("removes the code file once the session is killed", () => { @@ -186,20 +217,46 @@ describe("constraint session completion", () => { expect(names).toEqual(["a", "b"]); }); - it("lists the places after `state.places.`", () => { - const code = "return state.places."; - const server = makeServer(session("state", code)); - const completions = server.getCompletionsAtPosition( - CODE_PATH, - code.length, - undefined, - ); - const names = (completions?.entries ?? []).map((entry) => entry.name); - expect(names).toEqual(["P"]); - }); + it.each(["state.places.", "return state.places."])( + "lists places for %s", + (code) => { + const server = makeServer(session("state", code)); + const completions = server.getCompletionsAtPosition( + CODE_PATH, + code.length, + undefined, + ); + const names = (completions?.entries ?? []).map((entry) => entry.name); + expect(names).toEqual(["P"]); + }, + ); }); describe("constraint session HIR lint", () => { + it("accepts a state expression without requiring an explicit return", () => { + expect( + getHirDiagnosticsForItem( + "state.places.P.count > 3", + buildMetricContext(SDCPN, undefined, "boolean"), + ), + ).toEqual([]); + }); + + it("positions unsupported-call errors on the authored state expression", () => { + const code = "\nNumber.isFinite(state.places.P.count)"; + expect(constraintDiagnostics("state", code)).toEqual([]); + const diagnostics = getHirDiagnosticsForItem( + code, + buildMetricContext(SDCPN, undefined, "boolean"), + ); + expect(diagnostics).toHaveLength(1); + expect(diagnostics[0]).toMatchObject({ + source: "hir", + start: 1, + length: code.trim().length, + }); + }); + it("passes a boolean parameters-space expression through the constraint's lowering context", () => { const context = buildScenarioExpressionContext( SDCPN.parameters, diff --git a/libs/@hashintel/petrinaut-core/src/lsp/lib/generate-virtual-files.ts b/libs/@hashintel/petrinaut-core/src/lsp/lib/generate-virtual-files.ts index 3e9a2de2899..6c808eec5f1 100644 --- a/libs/@hashintel/petrinaut-core/src/lsp/lib/generate-virtual-files.ts +++ b/libs/@hashintel/petrinaut-core/src/lsp/lib/generate-virtual-files.ts @@ -6,7 +6,11 @@ import { getTransitionLogicAvailability, type PetrinautExtensionSettings, } from "../../extensions"; -import { AMBIENT_INPUT_NAMES, type DualFormSurfaceKind } from "../../hir"; +import { + AMBIENT_INPUT_NAMES, + detectStateConstraintForm, + type DualFormSurfaceKind, +} from "../../hir"; import { adHocSlotKey, isValidAdHocVariableName, @@ -822,9 +826,9 @@ export type ConstraintSessionData = { * * A `parameters` constraint is an expression over `scenario.*` and * `parameters.*`, wrapped like a scenario parameter override but checked as - * boolean. A `state` constraint is a function body over the metric `state` - * (`state.places.`) plus ambient `parameters`, wrapped like a - * metric body but checked as boolean. Empty code yields the defs file only. + * boolean. A `state` constraint is an expression or function body over the + * metric `state` plus ambient `parameters`, checked as boolean. Empty code + * yields the defs file only. */ export function generateConstraintSessionFiles( sdcpn: SDCPN, @@ -855,15 +859,17 @@ export function generateConstraintSessionFiles( files.set(defsPath, { content: buildMetricStateDefs(sdcpn) }); if (!emptyCode) { - files.set( - codePath, - metricBodyFile({ - defsPath, - functionName: "__constraint", - returnType: "boolean", - body: session.code, - }), - ); + const file = metricBodyFile({ + defsPath, + functionName: "__constraint", + returnType: "boolean", + body: session.code, + }); + if (detectStateConstraintForm(session.code) === "expression") { + file.prefix = `${file.prefix ?? ""}return (\n`; + file.suffix = `\n);${file.suffix ?? ""}`; + } + files.set(codePath, file); } return files; } diff --git a/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/scenario-to-ad-hoc-state.test.ts b/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/scenario-to-ad-hoc-state.test.ts index f59e258e903..46a603ef201 100644 --- a/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/scenario-to-ad-hoc-state.test.ts +++ b/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/scenario-to-ad-hoc-state.test.ts @@ -403,7 +403,7 @@ const synthesisContext = (net: SDCPN): AdHocSynthesisContext => ({ const compileOriginal = (candidate: Scenario, net: SDCPN) => { const result = compileScenario( candidate, - lowerScenarioToHir(candidate), + lowerScenarioToHir(candidate, { adHocContext: synthesisContext(net) }), net.parameters, net.places, net.types, @@ -441,13 +441,13 @@ const compileConverted = (candidate: Scenario, net: SDCPN) => { }; describe("adHocStateFromScenario over the core examples", () => { - it("covers every stored kind the examples ship", () => { - expect(exampleScenarios("per_place")).toHaveLength(22); - expect(exampleScenarios("code")).toHaveLength(2); - expect(exampleScenarios("adhoc")).toHaveLength(0); + it("stores every example scenario in the editable ad-hoc format", () => { + expect(exampleScenarios("per_place")).toHaveLength(0); + expect(exampleScenarios("code")).toHaveLength(0); + expect(exampleScenarios("adhoc")).toHaveLength(24); }); - it.each([...exampleScenarios("per_place"), ...exampleScenarios("code")])( + it.each(exampleScenarios("adhoc"))( "%s keeps every scenario parameter identifier verbatim", (_title, candidate, net) => { const converted = adHocStateFromScenario( @@ -466,14 +466,15 @@ describe("adHocStateFromScenario over the core examples", () => { }, ); - it.each(exampleScenarios("per_place"))( - "%s compiles to the same initial state and parameter values once converted", + it.each(exampleScenarios("adhoc"))( + "%s keeps its editable definition and compiled values when reopened", (_title, candidate, net) => { const converted = adHocStateFromScenario( candidate, synthesisContext(net), ); - expect(converted.kind).toBe("per_place"); + expect(converted.kind).toBe("adhoc"); + expect(converted.state).toEqual(candidate.initialState.content); const original = compileOriginal(candidate, net); const viaForm = compileConverted(candidate, net); expect(viaForm.initialState).toEqual(original.initialState); @@ -481,19 +482,9 @@ describe("adHocStateFromScenario over the core examples", () => { }, ); - it.each(exampleScenarios("code"))( - "%s keeps its code body and synthesizes the same parameters and overrides", + it.each(exampleScenarios("adhoc"))( + "%s synthesizes the same exposed parameters and overrides", (_title, candidate, net) => { - const converted = adHocStateFromScenario( - candidate, - synthesisContext(net), - ); - expect(converted.kind).toBe("code"); - if (converted.kind !== "code") { - return; - } - expect(converted.code).toBe(candidate.initialState.content); - expect(converted.state.places).toEqual({}); const synthesized = synthesizeConverted(candidate, net); expect(synthesized.scenarioParameters).toEqual( candidate.scenarioParameters, diff --git a/libs/@hashintel/petrinaut/docs/README.md b/libs/@hashintel/petrinaut/docs/README.md index 7770302f632..30faf45aa23 100644 --- a/libs/@hashintel/petrinaut/docs/README.md +++ b/libs/@hashintel/petrinaut/docs/README.md @@ -23,22 +23,24 @@ A quick map of the things you'll encounter: Petrinaut has three global modes in the top bar, though **Actual** is only enabled when the host application provides a live execution source: -- **Edit** -- the drawing/configuration workspace plus single-run simulation playback. +- **Edit** -- Canvas and Definitions views for editing the same net, plus single-run simulation playback on Canvas. - **Simulate** -- a separate management surface for scenarios and experiments. - **Actual** -- a read-only live-execution view supplied by a host such as Brunch. ## Contents -- [Drawing a Net](drawing-a-net.md) -- Top bar, canvas, sidebars, adding nodes and connecting arcs, selection, keyboard shortcuts, import/export, auto-layout. +- [Drawing a Net](drawing-a-net.md) -- Top bar, Canvas and Definitions views, sidebars, adding nodes and connecting arcs, selection, keyboard shortcuts, import/export, auto-layout. - [Petri Net Extensions](petri-net-extensions.md) -- Types, dynamics, transition kernels, firing rules, read/inhibitor arcs, as well as parameters and state visualizers. - [Useful Patterns](useful-patterns.md) -- Common modelling techniques, including duration and resource pools. - [Simulation](simulation.md) -- Set initial state, run a single simulation, use the timeline, control playback. - [Scenarios](scenarios.md) -- Save and switch between named simulation configurations. - [Ad-hoc Scenarios](ad-hoc-scenarios.md) -- The scenario form: define initial state and parameters inline for one run, or save them as a scenario. - [Experiments](experiments.md) -- Run Monte Carlo batches and inspect token-count distributions over time. +- [Simulation Panels](simulation-panels.md) -- Open scenarios and experiments beside the main view, expand to fullscreen, and use links and browser history. - [Actual Mode](actual-mode.md) -- View a host-provided live Petri net execution, currently via Brunch. - [Embedded Preview](preview.md) -- Explore a compact, read-only Petri net embedded in a host application. - [AI Assistant](ai-assistant.md) -- Build, review, and revise nets with text or inline Voice mode. -- [User Settings](visual-settings.md) -- Open preferences from any workspace tab and configure General, Viewport, Simulation, and Labs. +- [Code Editor](code-editor.md) -- Edit model functions and expand their sections within the Properties Panel. +- [User Settings](visual-settings.md) -- Open preferences from any workspace tab and configure General, Viewport, and Labs. - [Compilation Output](compilation-output.md) -- Inspect how your net's code compiled, and what stops it running on the GPU. - [Examples](examples.md) -- Walkthrough of the built-in example nets. diff --git a/libs/@hashintel/petrinaut/docs/ad-hoc-scenarios.md b/libs/@hashintel/petrinaut/docs/ad-hoc-scenarios.md index 6b0367e2b4e..cbc3bdbb774 100644 --- a/libs/@hashintel/petrinaut/docs/ad-hoc-scenarios.md +++ b/libs/@hashintel/petrinaut/docs/ad-hoc-scenarios.md @@ -9,25 +9,27 @@ Use an ad-hoc scenario for one-off runs and quick exploration. When you want to The same form appears in three places: 1. **Quick simulation** -- in the [Simulation Settings](simulation.md#simulation-settings) tab, with "No scenario" selected, the panel's two columns are the form's own tables: **Variables** above **Parameters** on the left, **Initial state** -- token counts and values -- on the right, no separate dialog. A quiet **Clear** button next to the Initial state title resets your entries. The next simulation run uses what you defined. Any [compile error](#errors) appears in the settings panel's error banner. -2. **Experiments** -- in the [create-experiment drawer](experiments.md#creating-an-experiment), choosing "No scenario" shows the form inside the Scenario section. The experiment's runs start from the state you defined, and the experiments table shows "Ad-hoc scenario" in its Scenario column. With [Parameter sweeps](experiments.md#parameter-sweeps) enabled, every numeric value carries an interval toggle (see below). +2. **Experiments** -- in the [create-experiment panel](experiments.md#creating-an-experiment), choosing "No scenario" shows the form inside the Scenario section. The experiment's runs start from the state you defined, and the experiments table shows "Ad-hoc scenario" in its Scenario column. Every numeric value carries a [Sweep](experiments.md#parameter-sweeps) interval toggle (see below). 3. **Scenario creation** -- [creating or editing a scenario](scenarios.md#creating-a-scenario) uses the same form with a **Scenario Parameter** toggle on each top-level Variable; see [Saving a scenario from the form](#saving-a-scenario-from-the-form). ## The form The form has up to three sections. Variables come first -- parameter overrides may read them: -- **Variables** -- named values (real, integer, boolean, or ratio -- a real between 0 and 1) written as `scenario.` in every expression below, exactly as scenario parameters are written in scenario code. Use them to drive many values from one number. Add one from the dimmed **Add a variable** line at the bottom of the list: like any cell, a first click selects it and a second click (or Enter, or its gutter's `+`) adds the variable -- or reach it with the down arrow from the last row; the fresh name opens ready to type. Each row starts with a small variable-glyph gutter whose menu offers **Delete variable**, and the add line's gutter shows a `+`. A variable's name edits like any other cell: select it, then press Enter (or click again) to edit, and Enter or Escape to leave. Its type select is a cell too: arrow keys move past it, Enter opens it. In the quick-simulation embedding, Variables sit above Parameters in the left column. +- **Variables** -- named values (real, integer, boolean, or ratio -- a real between 0 and 1) written as `scenario.` in every expression below, exactly as scenario parameters are written in scenario code. Use them to drive many values from one number. Add one from the dimmed **Add a variable** line at the bottom of the list: like any cell, a first click selects it and a second click (or Enter, or its gutter's `+`) adds the variable -- or reach it with the down arrow from the last row; the fresh name opens ready to type. Each row starts with a small variable-glyph gutter whose menu offers **Delete variable**, and the add line's gutter shows a `+`. A variable's name edits like any other cell: select it, then press Enter (or click again) to edit, and Enter or Escape to leave. Its type selector and compact dropdown chevron fit within the cell: arrow keys move past it, Enter opens it. In the quick-simulation embedding, Variables sit above Parameters in the left column. - **Parameters** -- one row per [net-level parameter](petri-net-extensions.md#global-parameters), showing its type and its value. An untouched parameter shows its default quietly, marked with a small `default` tag; enter an expression to override the value for this run -- it may read the Variables above. In the quick-simulation embedding this section sits under Variables in the left column, beside Initial state. -- **Initial state** -- one block per place in the net. Each place's title carries its token colour dot (grey for untyped places). +- **Initial state** -- one block per default starting place. Turn on the compact **Show all places** switch beside the Initial state title and info icon to include the other places. Turn it off to restore the filter. The switch appears only when there are other places to reveal. Each place's title carries its token colour dot (grey for untyped places). Filtering changes only what is visible; all place definitions are kept. -In the experiment drawer each section collapses: click the chevron in its header, or focus the header and press Left to collapse and Right to expand. Place headers inside Initial state collapse the same way everywhere, and a collapsed place shows a one-line summary of its rows and token total. In the quick-simulation embedding, places start collapsed. +In the experiment panel each section collapses: click the chevron in its header, or focus the header and press Left to collapse and Right to expand. Place headers inside Initial state collapse the same way everywhere, and a collapsed place shows a one-line summary of its rows and token total. In the quick-simulation embedding, places start collapsed. Left on an expanded place collapses it and keeps focus on its header. Press Left again to move to the neighbouring focus group. -Every value in the form is an expression. A first click selects a value; a second click, a double-click, or Enter opens the editor in place: a code input with completion and type checking at exactly the cell's position, the value's path (for example `Space › item 0 › x`) above it, and -- in the experiment drawer with sweeps enabled -- the interval toggle below it. Expressions may use your Variables (`scenario.`), net parameters (`parameters.`), and arithmetic -- the same [expression language](scenarios.md#expression-language) scenarios use. Press Enter, Escape, or click elsewhere to close the editor. Escape closes only the innermost thing that is open -- a completion list, a bound edit, the editor itself -- and never the drawer or dialog around the form; close those from their own buttons. Closing tidies a valid expression's formatting (spacing, redundant parentheses) without changing its meaning. A value may also be left **empty**: an empty cell reads as its type's neutral value -- 0 for numbers, `false` for booleans, `""` for text, the nil UUID -- shown grayed in the cell, and it is never an error. An empty dynamic-row count means 1 token; an empty place count means 0. +Every value in the form is an expression. A first click selects a value; a second click, a double-click, or Enter opens the editor in place: a code input with completion and type checking at exactly the cell's position, the value's path (for example `Space › item 0 › x`) above it, and -- in the experiment panel with sweeps enabled -- the interval toggle below it. Expressions may use your Variables (`scenario.`), net parameters (`parameters.`), and arithmetic -- the same [expression language](scenarios.md#expression-language) scenarios use. Press Enter, Escape, or click elsewhere to close the editor. Escape closes only the innermost thing that is open -- a completion list, a bound edit, the editor itself -- and never the panel or dialog around the form; close those from their own buttons. Closing tidies a valid expression's formatting (spacing, redundant parentheses) without changing its meaning. A value may also be left **empty**: an empty cell reads as its type's neutral value -- 0 for numbers, `false` for booleans, `""` for text, the nil UUID -- shown grayed in the cell, and it is never an error. An empty dynamic-row count means 1 token; an empty place count means 0. Opening a value with Enter or a second click selects its whole content, so typing replaces it. Opening by typing keeps the caret right after what you typed. ### Keyboard editing and undo +Section headers stack at the top as you scroll. Earlier headers fade slightly; click one to return to that section. Upcoming section headers stay at the bottom; click one to jump ahead. A soft fade marks the edge where content scrolls beneath the headers and clears when you return to the section's start. Spreadsheet text does not select when dragged; text selection remains available inside an open editor. + Every table in the form is a keyboard grid: arrow keys move between cells, phantom rows and type selects included, and moving up from a dynamic row's cells lands on its count strip, so counts and bounds are editable without the mouse. While an editor is open on a value that is just a number (or empty), the up and down arrows step it by 1 (by 10 with Shift held); a ratio steps by 0.1 (by 0.01 with Shift held) and stays between 0 and 1; on a boolean value, Up sets `true` and Down sets `false`; text and UUID values leave the arrows to the editor. Where the form lays its sections out as side-by-side columns (the quick-simulation embedding), vertical arrows stay within a column, and a horizontal arrow at a table's edge crosses into the neighbouring column, returning you to the cell you last used there. Tab keeps its usual browser behaviour throughout the form (inside an open row menu it dismisses the menu, as menus do). A token table's column headers are the grid's top line. In a token table, the left arrow from a row's first cell reaches the **row gutter**: focusing it highlights and selects the whole row, Enter opens the row's menu, and Delete removes the row. The menu is a keyboard menu too: it opens with the current kind focused, arrow keys move through the items, Enter chooses, and Escape returns to the gutter. Every row action lives in that menu -- the row kinds and **Delete row**. The walk does not stop at a table's edge: moving down from a table's last row continues to the next part of the form -- a section header, a place header, the next table -- and moving up continues backwards the same way. Collapsed sections are skipped. @@ -48,11 +50,11 @@ A place with a [token type](petri-net-extensions.md#typed-vs-untyped-places) is - **Fixed** (`#1`, `#2`, ...) -- the row emits exactly one token. - **Dynamic** (`i`, blue) -- the row emits many tokens: a quiet strip above the cells shows `×` and the row's **count expression**, and each cell is evaluated once per token with `i` running from `0` to `count - 1` (`count` is also available). The gutter's tooltip shows the row number. -- **Swept count** or **Optimized count** (`i`, purple; experiments with sweeps only, the word following the interval toggle's) -- a dynamic row whose count is a swept parameter: the strip shows the count's bounds, `× 0 … 12`. +- **Swept count** (`i`, purple; experiments with sweeps only) -- a dynamic row whose count is a swept parameter: the strip shows the count's bounds, `× 0 … 12`. Changing a row's kind never loses anything: its count (bounds included) is restored when you change back. The dimmed trailing row is a **phantom row**, and its cells follow the same selection model as every other cell: a first click selects one, and a second click (or Enter, or the row's `+` gutter) materializes a new fixed row. Remove a row from its gutter: the menu offers **Delete row**, and the Delete key removes it directly. In fixed rows, `i` is the row's position in the list and `count` is `1`. -Below the table, at the right, the place's **token total**: a number when every count resolves, otherwise the unresolved counts printed as they are (for example `2 + scenario.n_satellites tokens`). +Each typed place shows its **token total** at the top-right, aligned with its name, whether expanded or collapsed. This applies in Simulation Settings, scenario forms, and experiment forms, including computed-state previews. The total is a number when every count resolves, otherwise the unresolved counts printed as they are (for example `2 + scenario.n_satellites tokens`). ### Shared columns @@ -70,7 +72,7 @@ Every expression is type-checked as you work. The open editor marks problems inl ## Interval selections (experiments) -In the create-experiment drawer, with [Parameter sweeps](experiments.md#parameter-sweeps) enabled, every numeric value slot -- cells, counts, variables, shared columns, and net parameters -- carries a labeled interval toggle, purple while on: under the open cell editor, and on the row for Variables and Parameters. It reads **Sweep**, or **Optimize** when the [in-browser optimizer](experiments.md#optimizing-a-sweep) is on; the word is the same on every toggle of the form, and both mean the same thing. Turning it on replaces the expression with **Min** and **Max** cells; an interval declares nothing else, so there is no Scale or Step. Each bound is an expression cell with the same selection model as the rest of the form -- select it, press Enter (or click again) to edit, Enter or Escape to leave; Escape from a selected cell closes the editor. Turning the toggle off restores the expression you had, and the bounds are remembered too. A selected value shows its bounds (`0 … 12`) on a purple slot. Boolean and text values offer no toggle, and changing a selected Variable to boolean turns its toggle off; a cell muted by a shared column does not count. A row's gutter menu offers **Swept count** or **Optimized count** for a dynamic row's count, to match. +In the create-experiment panel, every numeric value slot -- cells, counts, variables, shared columns, and net parameters -- carries a labeled interval toggle, purple while on: under the open cell editor, and on the row for Variables and Parameters. It reads **Sweep**. Starting the [in-browser optimizer](experiments.md#optimizing-a-sweep) is a separate choice, during creation or later from the results. Turning it on replaces the expression with **Min** and **Max** cells; an interval declares nothing else, so there is no Scale or Step. Each bound is an expression cell with the same selection model as the rest of the form -- select it, press Enter (or click again) to edit, Enter or Escape to leave; Escape from a selected cell closes the editor. Turning the toggle off restores the expression you had, and the bounds are remembered too. A selected value shows its bounds (`0 … 12`) on a purple slot. Boolean and text values offer no toggle, and changing a selected Variable to boolean turns its toggle off; a cell muted by a shared column does not count. A row's gutter menu offers **Swept count** for a dynamic row's count. Each selection becomes a swept parameter of the experiment with a deterministic name, shown in the sweep navigator under the value's path (`Space › item 0 › x`): @@ -80,9 +82,9 @@ Each selection becomes a swept parameter of the experiment with a deterministic - `adhoc_var_net_` -- a top-level Variable; place-scoped variables use the place's name as the scope. - `adhoc_param_` -- a net parameter override. -Bounds must resolve to constants, integer values need integer bounds, and the maximum must exceed the minimum; a value that does not run shows its problem on the bound, and the drawer's footer names it. The experiment then behaves like any [parameter sweep](experiments.md#parameter-sweeps): the initial state compiles at the navigator's selection, parameter overrides follow each run's draw. Under **Optimize**, the study searches the generated parameters like any others; only [Constraints](experiments.md#constraints) need a saved scenario. +Bounds must resolve to constants, integer values need integer bounds, and the maximum must exceed the minimum; a value that does not run shows its problem on the bound, and the panel's footer names it. The experiment then behaves like any [parameter sweep](experiments.md#parameter-sweeps): the initial state compiles at the navigator's selection, parameter overrides follow each run's draw. When you start optimization, the study searches the generated parameters like any others; only [Constraints](experiments.md#constraints) need a saved scenario. -A saved scenario shown through the form in the experiment drawer offers the same toggle on each numeric scenario parameter row. +A saved scenario shown through the form in the experiment panel offers the same toggle on each numeric scenario parameter row. ## Saving a scenario from the form @@ -90,8 +92,8 @@ A saved scenario shown through the form in the experiment drawer offers the same Saving keeps your form entries as the scenario's definition, so editing the scenario reopens exactly the form you left. -Selecting a saved ad-hoc scenario in Simulation Settings shows it through the same form, read-only: only the scenario parameters (the exposed Variables) take value edits, for that run alone; auxiliary Variables stay hidden, and the parameter overrides and initial state can be browsed with the usual keyboard navigation but not changed. Editing any scenario opens this form: a scenario saved per place by an earlier version or by the AI assistant opens converted, and saving stores it in the form's format; a scenario that defines its initial state as code keeps that code, shown read-only -- edit its name, description, Variables and Parameters here, or recreate it from the form with a Dynamic row (see [Scenarios](scenarios.md#scenarios-stored-as-code)). Such a scenario stores no form entries, so every Variable must be marked **Scenario Parameter** to be kept -- the form refuses to save one that is not. +Selecting a saved ad-hoc scenario in Simulation Settings shows it through the same form, read-only: only the scenario parameters (the exposed Variables) take value edits, for that run alone. The Parameters and Initial state sections show computed values, updating when you change a scenario parameter. Select a value to see its source expression in a floating cell over the selected value. The expression disappears when focus moves away. Auxiliary Variables stay hidden. Editing any scenario opens this form: a scenario saved per place by an earlier version or by the AI assistant opens converted, and saving stores it in the form's format; a scenario that defines its initial state as code keeps that code, shown read-only -- edit its name, description, Variables and Parameters here, or recreate it from the form with a Dynamic row (see [Scenarios](scenarios.md#scenarios-stored-as-code)). Such a scenario stores no form entries, so every Variable must be marked **Scenario Parameter** to be kept -- the form refuses to save one that is not. ## Errors -Ad-hoc definitions are validated as you type, on the value they belong to, and again when you run. In quick simulation, compile problems also appear in the Simulation Settings error banner; in the experiment drawer, in the footer. +Ad-hoc definitions are validated as you type, on the value they belong to, and again when you run. Hover over an underlined value to read its error, or open the value editor to see the error below the input. In quick simulation, compile problems also appear in the Simulation Settings error banner; in the experiment panel, in the footer. diff --git a/libs/@hashintel/petrinaut/docs/ai-assistant.md b/libs/@hashintel/petrinaut/docs/ai-assistant.md index 40e6d4684d9..84ade13f3e4 100644 --- a/libs/@hashintel/petrinaut/docs/ai-assistant.md +++ b/libs/@hashintel/petrinaut/docs/ai-assistant.md @@ -11,13 +11,15 @@ Open the assistant in any of these ways: 3. **First-run prompt**. When you load Petrinaut against an empty net, a centred prompt appears. Type a description and its trailing action becomes **Send**; select it to open the panel with your message already in flight. When the host provides Voice mode, the empty prompt instead shows a waveform action titled **Start voice mode**. It opens the same assistant without creating an empty text message. Dismiss the prompt with the **X**, by clicking outside it, or by pressing **Escape**; it is hidden for the rest of the session once dismissed. 4. **Command palette**. Choose **Toggle AI assistant**, or press **Cmd/Ctrl+Shift+K** directly. This opens the assistant and focuses the message field, or closes it when already visible, including compact Voice mode. Reopening preserves the conversation and docked or floating layout. The command keeps you in your current view. This command is available when the host provides an AI assistant. -The assistant stays available across **Edit**, **Simulate**, **Actual**, and **Notebook** modes. Switching views preserves your conversation, draft, and active response. The panel resizes by dragging its left edge. Text and voice share the **AI** transcript. Some hosts add a second tab, such as **Workpiece**, for a saved document. Select a tab to switch views, or use the left/right arrow keys while a tab is focused. Switching does not end a response, clear your draft or interrupt Voice; the composer and active controls remain available. +The assistant stays available across **Edit** (Canvas or Definitions), **Simulate**, and **Actual** modes. Switching views preserves your conversation, draft, and active response. The panel resizes by dragging its left edge. Text and voice share the **AI** transcript. Some hosts add a second tab, such as **Workpiece**, for a saved document. Select a tab to switch views, or use the left/right arrow keys while a tab is focused. Switching does not end a response, clear your draft or interrupt Voice; the composer and active controls remain available. ### Docking and floating The assistant opens in a sidebar at the far right of the editor. It sits flush against the viewport, beside the canvas and its properties panel. Its left divider and resize highlight span the full panel height. The sidebar slides in at its full width while the canvas makes room. Closing it returns that space to the canvas. -The bottom toolbar stays centered on the editor when the docked assistant opens, moving only as far as needed to avoid overlapping the panels. +Experiment and Scenario panels make room for the docked assistant too. Their fullscreen presentation fills the main view beside it. Resizing or closing the assistant adjusts the available space; floating the assistant lets it sit above the open panel. + +The bottom toolbar stays centered on the remaining main view, moving when needed to avoid overlapping its panels. Choose **Float AI assistant** in the header to detach it into a rounded panel over the canvas. The canvas expands smoothly to reclaim the sidebar's space, and the floating panel reserves no space at the right edge. Drag anywhere in the header outside the tabs and action buttons to move it, or focus **Move AI assistant** and use the arrow keys. Hold **Shift** with an arrow key to move farther. The floating panel stays within the editor when the window changes size. @@ -234,7 +236,7 @@ ranges to minimize or maximize a metric. The experiment appears in a compact card with its status, run count, and results. Simulation cards use blue; optimization cards use purple and glow while running. Select **View -experiment** to inspect metric distributions in the Experiments drawer. The +experiment** to inspect metric distributions in the Experiments panel. The heatmap shows how values spread across runs; click a time step to see its histogram. Select **Cancel** to stop its work. The assistant receives the diff --git a/libs/@hashintel/petrinaut/docs/code-editor.md b/libs/@hashintel/petrinaut/docs/code-editor.md new file mode 100644 index 00000000000..80b36871d7e --- /dev/null +++ b/libs/@hashintel/petrinaut/docs/code-editor.md @@ -0,0 +1,28 @@ +# Code Editor + +Edit model functions in the Properties Panel. Expand a code section to fill the panel while keeping its controls available. + +## Open code + +Select a place, transition, or differential equation in the canvas or left sidebar. Open its code section in the Properties Panel: + +- **Firing Time** contains a transition’s predicate or stochastic rate. +- **Transition Results** contains a transition’s output function. +- **Differential Equation** contains the equation’s properties and code. +- **Visualizer** contains a place’s visualization code and preview. + +The item’s menu in the left sidebar opens its code directly. For a place with dynamics, **View equation** below its assigned differential equation opens that equation’s properties and code. + +## Fill the panel + +Use the **Fill panel** button in the section header to expand that section to the full width and height of the Properties Panel’s section area. Its controls stay beside the code. The canvas and the panel’s width stay unchanged. + +The expanded header shows a breadcrumb, such as **Transition Collision › Transition Results**, using the parent’s usual icon and title. Long parent names fade so the code section’s name stays visible. Click the parent name to return to the item’s other properties. Code, undo history, cursor position, and the other sections’ sizes and contents remain in place. + +Expanding and returning animate within the section container. These transitions respect the animation setting and your system’s reduced-motion preference. + +On hosts with app navigation enabled, expanding a section updates the URL and browser history. **Back** closes the expanded view and **Forward** reopens it. Reloading or opening that URL restores the selected item and expanded section. + +Drag the panel’s left edge to change its width. After returning to the other properties, drag the dividers between sections to change their heights. + +Changes apply automatically to the model. Read-only files and simulation modes keep code read-only. Selecting another item or changing the file or active subnet restores the usual section layout. diff --git a/libs/@hashintel/petrinaut/docs/compilation-output.md b/libs/@hashintel/petrinaut/docs/compilation-output.md index 071cf88ac72..be3ad149165 100644 --- a/libs/@hashintel/petrinaut/docs/compilation-output.md +++ b/libs/@hashintel/petrinaut/docs/compilation-output.md @@ -1,6 +1,6 @@ # Compilation Output -The **Compilation** tab explains what Petrinaut's compiler made of your net's code: which conditions, kernels, differential equations and metrics were understood, and what stops the net running on the [GPU backend](experiments.md#compute-backend-experimental). +The **Compilation** tab explains what Petrinaut's compiler made of your net's code: which conditions, kernels, differential equations and metrics were understood, and what stops the net running on the [GPU backend](experiments.md#compute-backend). It is a diagnostic view about the compiler, not about your model — for errors in your code, use [Diagnostics](petri-net-extensions.md#diagnostics) instead. diff --git a/libs/@hashintel/petrinaut/docs/drawing-a-net.md b/libs/@hashintel/petrinaut/docs/drawing-a-net.md index d5979178fa5..ce6f708d889 100644 --- a/libs/@hashintel/petrinaut/docs/drawing-a-net.md +++ b/libs/@hashintel/petrinaut/docs/drawing-a-net.md @@ -5,6 +5,7 @@ The editor is organized around a central canvas where you build your net: - **Top bar** -- net management menu, optional title field, **Edit / Simulate / Actual** mode switcher, active-experiments indicator, recent-changes history. See [Top bar](#top-bar). +- **Canvas / Definitions** -- a compact selector floating over Canvas, or beside search in the Definitions toolbar. - **Canvas** (center) -- the main workspace where places and transitions are displayed and connected. - **Left sidebar** -- the Entities tree: every node, token type, differential equation and parameter in the net, in one hierarchy. See [Left sidebar](#left-sidebar). - **Properties panel** (right) -- opens when you select an entity, showing its configurable properties. @@ -20,6 +21,16 @@ into it, and the rest comes back for as long as you stay on it. full-editor +## Properties panel + +Select an entity to inspect its properties. Places use the same Name and Description fields as transitions and types. + +Place settings include an **Options** section for **Component port**, **Token capacity**, and **Default starting place**. Click a checkbox or its label to toggle the setting. Enabling Token capacity expands a compact **Maximum tokens** row beneath it. Point at the info icons for an explanation of each option. + +Click **View type** or **View equation** to open the assigned definition. These navigation actions are available when the net is read-only. + +The **State** and **Visualizer** section headers have visible expand arrows. Click a header to expand its content and drag the divider to give it more room. The Visualizer switch remains available while its section is collapsed. + ## Top bar Spans the full editor width and has three sections. @@ -44,11 +55,11 @@ Spans the full editor width and has three sections. Petrinaut global modes are switched via the centre control in the top bar. -| Mode | Workspace | -| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Edit** | Canvas + left sidebar + properties panel + bottom panel + bottom toolbar (with AI assistant). This is where you draw the net, configure entities, write code, and run single simulations. | -| **Simulate** | Replaces the workspace with the [Scenarios](scenarios.md) and [Experiments](experiments.md) management views. | -| **Actual** | Shows a host-provided live execution source. It is disabled unless the host provides Actual-mode data. See [Actual Mode](actual-mode.md). | +| Mode | Workspace | +| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Edit** | Canvas or Definitions. Draw and connect nodes on Canvas, or edit the same net as expandable cells in Definitions. Canvas also provides single-run simulation controls. | +| **Simulate** | Replaces the workspace with the [Scenarios](scenarios.md) and [Experiments](experiments.md) management views. | +| **Actual** | Shows a host-provided live execution source. It is disabled unless the host provides Actual-mode data. See [Actual Mode](actual-mode.md). | In Simulate mode the net structure becomes read-only -- you can still manage scenarios and experiments. You cannot change places, transitions, arcs, types, or parameters. Switch back to Edit mode to modify the net. @@ -56,6 +67,30 @@ In Actual mode the net is also read-only. It shows the Petri net supplied by the Switching modes does not stop background experiments. The active-experiments indicator remains visible in the top bar from any mode. +## Canvas and Definitions + +In **Edit**, use **Canvas / Definitions** to switch views. On Canvas, the selector +floats below the main header, beside the Entities sidebar when it is open. +In Definitions, it shares the search and filter toolbar. The compact selector +becomes fully opaque when hovered or focused. Both views work on +the same net and share the selected entity. +Switching between them keeps the canvas position and zoom, and the Definitions view’s +scroll position, expanded cells, search, and filters. + +**Canvas** shows the diagram, sidebars, Properties Panel, and simulation +controls. Use it to add or remove nodes and arcs and to arrange the net. + +**Definitions** lists places, transitions, types, differential equations, and +parameters as expandable cells. Open a cell to edit its name, fields, arc +weights, type assignments, or code. Use the search and kind filters to narrow +the list, or switch its order between document and dependency order. The graph +on the right shows how the selected entity connects to the rest of the net. + +On the website, the URL +records the selected view; Back and Forward switch views, and reloading a +Definitions link opens Definitions again. Simulate and Actual keep their own +workspaces. + ## Adding places and transitions Use the bottom toolbar to add nodes: diff --git a/libs/@hashintel/petrinaut/docs/examples.md b/libs/@hashintel/petrinaut/docs/examples.md index 6237c5cacfd..bff52a8dca0 100644 --- a/libs/@hashintel/petrinaut/docs/examples.md +++ b/libs/@hashintel/petrinaut/docs/examples.md @@ -2,6 +2,8 @@ Petrinaut includes several built-in example nets accessible from the **hamburger menu** (top-left) under **Load example**. The SIR model is the simplest starting point; the Supply Chain with Disruption net is the most complex. +Bundled scenarios use the [ad-hoc scenario form](ad-hoc-scenarios.md), so you can edit their Variables, parameter overrides, and initial-state tables. Dynamic rows keep generated machine fleets and satellite constellations linked to their scenario parameters. + ## SIR Epidemic Model The classic Susceptible-Infected-Recovered compartmental model from epidemiology, implemented as a stochastic Petri net. @@ -171,12 +173,12 @@ An orbital mechanics simulation: satellites are continuously launched into orbit - **`Distribution.map()` for coordinate conversion** -- a uniform launch angle is sampled once, then `.map()` derives both `x` (cosine) and `y` (sine) from the same underlying sample for a coherent polar-to-cartesian position. - **Predicate transitions based on geometry** -- "Collision" checks the distance between two satellites and "Crash" checks distance from the planet's surface, routing tokens to the Debris place. - **Arc weight 2** on the "Collision" transition -- it consumes two satellites from the Space place at once to evaluate pairwise proximity. -- **Scenarios** -- _Moon Orbit_ (low gravity, gentle arcs) and _Earth Orbit_ (high orbital velocities, frequent launches) preconfigure the gravitational constant, planet radius, and launch parameters. _Pre-deployed Constellation_ defines its initial state [as code](scenarios.md#scenarios-stored-as-code), building a ring of satellites with `range(...).map(...)` from two scenario parameters (`number_of_satellites`, `initial_altitude`). Each satellite starts tangentially at circular-orbit speed, so the whole ring stays in orbit from the first frame. +- **Scenarios** -- _Moon Orbit_ (low gravity, gentle arcs) and _Earth Orbit_ (high orbital velocities, frequent launches) preconfigure the gravitational constant, planet radius, and launch parameters. _Pre-deployed Constellation_ uses a dynamic row in the [ad-hoc scenario form](ad-hoc-scenarios.md) to build a ring of satellites from two scenario parameters (`number_of_satellites`, `initial_altitude`). Each satellite starts tangentially at circular-orbit speed, so the whole ring stays in orbit from the first frame. - **[Metrics](simulation.md)** -- satellites in orbit, debris objects, average orbital radius, and average orbital speed. Its **Average orbital radius** and **Average orbital speed** metrics reduce over the satellites' attributes and compile to the GPU as loops. **Suggested initial state:** no initial tokens needed -- pick a scenario (e.g. _Earth Orbit_) and press Play. The "LaunchSatellite" source transition creates satellites with randomized orbital positions and velocities. Select the Space place and open the visualizer preview to watch the orbits fill up. The velocity for a roughly circular orbit at radius `r` is approximately `sqrt(gravitational_constant / r)`. -**Bundled extras:** five scenarios -- **Moon Orbit**, **Earth Orbit**, **Mars Orbit**, and **Solar Orbit** tune the physical constants for very different orbital regimes, and **Pre-deployed Constellation** starts with a configurable ring of satellites already in orbit (its initial state is stored as code). Switch between them in Simulation Settings to compare. +**Bundled extras:** five scenarios -- **Moon Orbit**, **Earth Orbit**, **Mars Orbit**, and **Solar Orbit** tune the physical constants for very different orbital regimes, and **Pre-deployed Constellation** starts with a configurable ring of satellites already in orbit (its initial state is editable as a dynamic row). Switch between them in Simulation Settings to compare. **Key concepts:** [dynamics](petri-net-extensions.md#differential-equations-dynamics), [visualizers](petri-net-extensions.md#visualizer), [source transitions](useful-patterns.md#source-transitions-exogenous-arrivals), [distributions and `.map()`](petri-net-extensions.md#distributions), [arc weight](useful-patterns.md#arc-weight-for-multi-token-operations), [scenarios](scenarios.md). diff --git a/libs/@hashintel/petrinaut/docs/experiments.md b/libs/@hashintel/petrinaut/docs/experiments.md index d0aac65fa4f..69cfeddf8d0 100644 --- a/libs/@hashintel/petrinaut/docs/experiments.md +++ b/libs/@hashintel/petrinaut/docs/experiments.md @@ -7,9 +7,9 @@ Experiments live under the **Simulate** [global mode](drawing-a-net.md#global-mo ## Creating an experiment 1. Switch to **Simulate** mode and open the **Experiments** tab. -2. Click **Create**. The Create Experiment drawer opens. +2. Click **Create**. The Create Experiment panel opens. 3. Fill in the configuration (see below). -4. Click **Run** -- **Create sweep** when a value is swept, **Optimize** when the in-browser optimizer will search it. The button reads **Starting** (or **Creating**) while the experiment starts. +4. Click **Run** -- **Create sweep** when a value is swept, **Optimize** when **Start optimizer immediately** is checked. The button reads **Starting** (or **Creating**) while the experiment starts. ### Experiments created by the assistant @@ -31,48 +31,54 @@ experiments are not restored after a reload. ### Configuration -| Setting | Default | Notes | -| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Name** | `Experiment` | Free text. | -| **Scenario** | the first saved scenario, or **No scenario** when none is saved | **No scenario** -- the [scenario form](ad-hoc-scenarios.md) below the picker, or one of your saved [scenarios](scenarios.md). An experiment runs against exactly one scenario. | -| **Scenario parameters** | each scenario parameter's default | When a scenario is selected, you can override its scenario parameters per experiment. Expressions are evaluated once at start. Each numeric parameter also has an interval toggle — **Sweep**, or **Optimize** with In-browser optimization on — see [Parameter sweeps](#parameter-sweeps). | +| Setting | Default | Notes | +| ----------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Name** | `Experiment` | Free text. | +| **Scenario** | the first saved scenario, or **No scenario** when none is saved | **No scenario** -- the [scenario form](ad-hoc-scenarios.md) below the picker, or one of your saved [scenarios](scenarios.md). An experiment runs against exactly one scenario. | +| **Scenario parameters** | each scenario parameter's default | When a scenario is selected, you can override its scenario parameters per experiment. Expressions are evaluated once at start. Each numeric parameter also has a **Sweep** interval toggle — see [Parameter sweeps](#parameter-sweeps). | -With "No scenario" selected, the Scenario section shows the [ad-hoc scenario form](ad-hoc-scenarios.md): define the initial state and parameter values inline for this experiment, without saving a scenario. Left untouched, the experiment runs from the manually-set markings and defaults. The experiments table shows "Ad-hoc scenario" in its Scenario column for such runs. With [Parameter sweeps](#parameter-sweeps) enabled, every numeric value of the form carries the same interval toggle -- see [Interval selections](ad-hoc-scenarios.md#interval-selections-experiments). +With "No scenario" selected, the Scenario section shows the [ad-hoc scenario form](ad-hoc-scenarios.md): define the initial state and parameter values inline for this experiment, without saving a scenario. Left untouched, the experiment runs from the manually-set markings and defaults. The experiments table shows "Ad-hoc scenario" in its Scenario column for such runs. Every numeric value of the form carries a [Sweep](#parameter-sweeps) interval toggle -- see [Interval selections](ad-hoc-scenarios.md#interval-selections-experiments). -With a scenario selected, the Scenario section shows it through the same form: the scenario parameters take value edits in worksheet style -- a ratio parameter's edit applies only between 0 and 1; outside, the form marks it and the run keeps the previous value -- each numeric one with the interval toggle when Parameter sweeps is on, and a collapsed **Computed state** sub-section underneath previews the exact parameter values and initial tokens each run will start with -- computed only when you open it, and recomputed as you change the values above. A swept parameter previews at the start of its range, the first combination the sweep runs, and the preview says so. The preview sits in its own tinted panel and scrolls as one, so a net with many places leaves the rest of the drawer in reach. +With a scenario selected, the Scenario section shows it through the same form: the scenario parameters take value edits in worksheet style -- a ratio parameter's edit applies only between 0 and 1; outside, the form marks it and the run keeps the previous value -- each numeric one with a **Sweep** interval toggle, and a collapsed **Computed state** sub-section underneath previews the exact parameter values and initial tokens each run will start with -- computed only when you open it, and recomputed as you change the values above. A swept parameter previews at the start of its range, and the preview says so. The preview scrolls within a bordered panel, with Parameters and Initial state headers that stack as you scroll. Click a faded earlier header to return to its section, or an upcoming header at the bottom to jump ahead. Scrollbars overlay the content when you hover over a scrollable area, without shifting the columns. It initially shows default starting places; turn on **Show all places** beside Initial state to inspect the rest. The switch appears only when there are other places. Select a computed value to see its source expression in a floating cell over the selected value. The expression disappears when focus moves away. | **Runs** | `1000` | Positive integer; how many independent simulations to run. For a sweep the field reads **Max runs per selection**: each selection refines progressively (8, 25, 100, … 1000, 5000, …) up to this ceiling, so large budgets — 100,000 on the GPU — sharpen the distribution the longer you stay. | | **Time step (dt)** | `0.1` | Same meaning as in single-run simulations (see [Simulation](simulation.md#time-step-dt)). | | **Max time (seconds)** | `180` | Each run advances until simulation time reaches this value, then completes. | -| **Run on GPU** | off | Only shown when **WebGPU** is on under **Settings → Simulation**. Greyed out with the reason on hover when this model cannot run on the GPU. See [Compute backend](#compute-backend-experimental). | +| **Run on GPU** | off | Shown when your browser supports WebGPU. Greyed out with the reason on hover when this model cannot run on the GPU. See [Compute backend](#compute-backend). | -The model used is a snapshot of the current net at the time you press **Run**. Editing the net afterwards does not change runs that have already started. +The model used is a snapshot of the current net at the time you press **Run**. Later runs and optimization use that snapshot, even if you edit the net afterwards. > Currently, an experiment can only run against one scenario at a time. To compare scenarios, create one experiment per scenario. +### Metrics + +Click **Add metric** and choose **Place tokens**, **Transition firing**, or a custom metric. Built-in metric names follow the selected place or transition automatically, such as **Queue tokens** or **Dispatch firing (cumulative)**. Transition names include the **Per frame** or **Cumulative** count mode. Existing model metrics display their saved name as read-only text. Only **Custom code** metrics have an editable name. Use the metric type picker to switch between model metrics, custom code, and built-in metrics at any time. Choosing **Custom code** after a model metric makes its code editable. + ### Constraints -With [Parameter sweeps](#parameter-sweeps) and [In-browser optimization](visual-settings.md#in-browser-optimization-experimental) both on, flipping the first **Optimize** toggle on a saved scenario's parameter adds a **Constraints** section to the drawer, between [Objective](#optimizing-a-sweep) and Metrics. Its rows record boolean conditions the optimizer must respect when it [drives the sweep](#optimizing-a-sweep). The sweep itself ignores them: no run is excluded from the charts and the objective is never changed by them. An experiment created with **No scenario** has no Constraints section: the names of its generated parameters are not yours to write. Two kinds, added from the **Parameter constraint** and **State constraint** buttons under the list and mixed in one list, each row marked with a **Parameters** or **State** chip: +When the host application provides an in-browser optimizer, turning on **Sweep** for a saved scenario's parameter adds a **Constraints** section after Metrics. You can set constraints before starting optimization, whether you start it during creation or later. Its rows record boolean conditions the optimizer must respect when it [drives the sweep](#optimizing-a-sweep). The sweep itself ignores them: no run is excluded from the charts and the objective is never changed by them. An experiment created with **No scenario** has no Constraints section: the names of its generated parameters are not yours to write. Conditions are grouped under **Parameters** and **State**, each with its own **Add** button: - **Parameter constraints** -- one-line expressions over the sweep's parameters (`scenario.*` for scenario parameters, `parameters.*` for net parameters) that must produce a boolean, for example `scenario.min_load < scenario.max_load`. Before a step runs, the optimizer checks them at the step's values, snapped to the sweep's grid. A step whose values break one is **infeasible**: it costs one step and no simulation, the sliders do not move to it, it is reported as pruned with the constraint named, and its row is greyed in the steps table. -- **State constraints** -- small code bodies that read the simulation `state` exactly like a metric and `return` a boolean, for example `return state.places.Queue.count <= 10;`. Every run of a step reports whether the condition held on every sampled frame: a run **passed** when it did and **failed** otherwise, and a run that errors reports neither, so its step's fraction is over the runs that reported. A state constraint runs beside the sweep's metrics on every batch, from the sweep's creation on, and it runs on the CPU: the WebGPU switch greys out while a state row is drafted, and a sweep with state constraints computes on the CPU whether or not a study drives it. +- **State constraints** -- boolean expressions that read the simulation `state`, for example `state.places.Queue.count <= 10`. Petrinaut supplies the `return` internally. Every run of a step reports whether the condition held on every sampled frame: a run **passed** when it did and **failed** otherwise, and a run that errors reports neither, so its step's fraction is over the runs that reported. A state constraint runs beside the sweep's metrics on every batch, from the sweep's creation on, and it runs on the CPU: the WebGPU switch greys out while a state row is drafted, and a sweep with state constraints computes on the CPU whether or not a study drives it. -A step's verdict comes from its runs. The **Pass threshold**, one setting for the whole sweep shown in the section's header once a State row exists, is the share of a step's runs that must pass (95 percent by default, an alpha of 0.05). A step is **clear** when every state constraint held on at least that share of its runs and **limited** when one fell short. Every rate in the results is printed as its raw fraction beside the percentage, `52 / 60 · 87%`, so the run count behind a percentage is always in view. +A step's verdict comes from its runs. The **Pass threshold**, beside the State heading once a state condition exists, is the share of a step's runs that must pass (95 percent by default, an alpha of 0.05). A step is **clear** when every state constraint held on at least that share of its runs and **limited** when one fell short. Every rate in the results is printed as its raw fraction beside the percentage, `52 / 60 · 87%`, so the run count behind a percentage is always in view. -Each row checks as you type: type errors, unknown names and a result that is not a boolean are underlined, and the message reads in the line under the row. Typing `scenario.`, `parameters.` or `state.places.` offers completions, and hovering a name shows its type. **Optimize** stays disabled, with the first failing row named in the footer, until every row compiles; the rows are compiled once more when you press it. Empty rows are ignored, and removing a row is its trash button. Changing the scenario clears the rows. The constraints are recorded with the experiment: once created, the sweep's **Parameters** card lists them behind **Show N constraints** in its footer, one line per constraint with its kind, its label (**Parameter constraint 1**, **State constraint 1**, in the order you added them) and its code, and the pass threshold under them when a state constraint exists. +State editors start on one line and grow with the code up to eight lines, then scroll. Use **Enter** to split a long expression across lines. For intermediate calculations, a code body with an explicit `return` still works, for example `const count = state.places.Queue.count; return count <= 10;`. Empty fields show expression examples using the current scenario and model where their names allow dot access. + +Each row checks as you type: type errors, unknown names and a result that is not a boolean are underlined, and the message reads in the line under the row. Typing `scenario.`, `parameters.` or `state.places.` offers completions, and hovering a name shows its type. **Create sweep** or **Optimize** stays disabled, with the first failing row named in the footer, until every row compiles; the rows are compiled once more when you press it. Empty rows are ignored. Each condition has a trash button beside its editor; removing it moves focus to another condition in the same group, or to that group's **Add** button. Changing the scenario clears the rows. The constraints are recorded with the experiment: once created, the sweep's **Parameters** card lists them behind **Show N constraints** in its footer, one line per constraint with its kind, its label (**Parameter constraint 1**, **State constraint 1**, in the order you added them) and its code, and the pass threshold under them when a state constraint exists. ## Lifecycle and statuses Experiments progress through these status labels: -| Status | Meaning | -| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Initializing** | The experiment has been created and its workers are starting up. | -| **Running** | Runs are in progress. | -| **Idle** | A sweep computing nothing: fresh, or its selected region fully sampled. Moving a parameter control resumes running. Grey in the list. | -| **Optimizing** | A sweep whose sliders a study drives (see [Optimizing a sweep](#optimizing-a-sweep)). An experiment created with **Optimize** opens in this state. The drawer's header reads it; the list keeps the sweep's own status, Running or Idle. | -| **Complete** | All runs finished without error. | -| **Error** | The experiment failed to start or hit an unrecoverable error. The drawer shows the error message. For a sweep the error belongs to the selection that failed: move a control and the next selection computes normally. | -| **Cancelled** | You clicked **Cancel**, or the experiment was cancelled. | +| Status | Meaning | +| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Initializing** | The experiment has been created and its workers are starting up. | +| **Running** | Runs are in progress. | +| **Idle** | A sweep computing nothing: fresh, or its selected region fully sampled. Moving a parameter control resumes running. Grey in the list. | +| **Optimizing** | A sweep whose sliders a study drives (see [Optimizing a sweep](#optimizing-a-sweep)). Optimization can start during creation or later from the Parameters card. The panel's header reads it; the list keeps the sweep's own status, Running or Idle. | +| **Complete** | All runs finished without error. | +| **Error** | The experiment failed to start or hit an unrecoverable error. The panel shows the error message. For a sweep the error belongs to the selection that failed: move a control and the next selection computes normally. | +| **Cancelled** | You clicked **Cancel**, or the experiment was cancelled. | Experiments run in background Web Workers, so simulation playback and editor interactions stay responsive. Multiple experiments can run concurrently. @@ -87,46 +93,57 @@ Two consequences worth knowing: - Progress reports the slowest worker's position, so the progress bar never runs ahead of the results behind it. - Several experiments running at once each use the same number of workers, so they compete for cores and all of them slow down. Run them one at a time if you want any single one to finish as fast as possible. -### Parameter sweeps +### Keyboard navigation + +Use arrow keys to move between the form's sections, fields, scenario tables, metrics, and footer actions. Left collapses an expanded section or place; Right expands it. Text fields keep Left and Right for moving the caret until it reaches an edge. Tab follows the usual browser order. + +The creation panel opens with **Name** focused. Use **Tab** and **Shift+Tab** to move through fields and actions. Use the arrow keys in dropdowns and radio groups, and **Enter** or **Space** to activate buttons and section toggles. + +In metric and constraint code fields, **Tab** moves to the next control. **Enter** adds a line in state conditions and custom metrics. **Escape** dismisses code suggestions first, then returns focus to the metric or constraint row. From an ordinary field or section header, **Escape** closes the panel. The scenario worksheet uses its own [keyboard navigation](ad-hoc-scenarios.md). Removing a row moves focus to the next row, the previous row, or the add button when no rows remain. + +## Parameter sweeps -Parameter sweeps are experimental and off by default. Turn on **Parameter sweeps** under Simulation in the [settings dialog](visual-settings.md#parameter-sweeps-experimental) to get the interval toggle on every numeric value of the experiment form: it reads **Sweep** on its own, and **Optimize** once [In-browser optimization](visual-settings.md#in-browser-optimization-experimental) is on with an optimizer that runs in your browser -- a host whose optimizer runs elsewhere keeps **Sweep**, since a sweep can only be optimized in the browser. Either word means the same thing on the value: an interval instead of one number. +Every numeric value in the experiment form offers a **Sweep** toggle. It selects an interval instead of one number. Optimization is a separate choice. Flip the toggle on any numeric value to explore an interval of values instead of one. Set the minimum and the maximum — that is all a sweep declares. Petrinaut quantizes the interval finely (about fifty steps; integer parameters step by whole numbers) so a selection has a stable identity and revisiting one restores its results. With **No scenario** selected, the same toggle sits on every numeric value of the [form](ad-hoc-scenarios.md) -- a token count, a cell, a variable, a parameter override -- and each selection sweeps as a generated parameter named after the value, shown in the navigator under the value's path. -A sweep computes **what you have selected**, and nothing until something selects. A sweep created with **Create sweep** sits idle with every slider spanning its whole interval, its charts empty, and the line under the sliders says what to do -- collapse a control to a point or click the surface to compute a point, widen a range to sample across it -- until you move a control or click the surface. A sweep created with **Optimize** is selected by its study from its first step (see [Optimizing a sweep](#optimizing-a-sweep)). The results drawer grows a **Parameters** card across the top of its body, with one slider per swept parameter and the swept count under its title. Each slider selects a range on its interval, and starts spanning the whole of it: +An invalid interval keeps **Create sweep** or **Optimize** disabled. The first configuration error appears in the footer and the disabled button's tooltip. -- **Range** (the default): Petrinaut runs **one stochastic simulation over the ranges** — every run draws its own value for each ranged parameter, spread across the selected interval — and the metric charts stream the live distribution **over the region**, sharpening exactly like a plain experiment's. Resize a range from either end to focus; compute restarts on the new selection. Range selections run on the GPU when the net qualifies — each run's parameter draw is uploaded alongside its state — and otherwise on the CPU at full parallelism; an initial state that a scenario derives from a ranged parameter holds at the range's midpoint, while the simulation itself reads each run's own value. -- **Point**: switch a parameter's control to Point and its slider collapses to a single value. A point refines in escalating batches (8, 25, 100, … up to your run budget), exactly like a plain experiment at that value — including on the GPU. +The **Parameters** card has one slider per swept parameter. Each slider chooses a single value within the bounds set when creating the experiment. Its name and current value appear together above the slider, with the minimum and maximum in small text below its left and right ends. Parameter identifiers appear as readable names, such as **Transmission rate** for `transmission_rate`. -Move a slider and compute immediately restarts on the new selection, like a raytracer dropping its rays when the camera moves. While the new selection's first results compute, the charts keep the previous selection's picture dimmed rather than going blank, then fade it out quickly as fresh data draws in underneath. Every selection you have computed keeps its results: returning to the same point or the same range restores its runs and distributions instantly, and refinement resumes where it left off. Resizing a range is a new selection and computes afresh. +A sweep created with **Create sweep** starts at the middle of each parameter's interval and waits for you to move a slider or click the Surface. The line below the controls reads **Move a slider to explore results**. A sweep created with **Optimize** follows the values selected by its study from the first step (see [Optimizing a sweep](#optimizing-a-sweep)). + +Moving a slider starts sampling at the displayed values. Samples arrive in increasing batches (8, 25, 100, … up to the sampling limit), on the GPU when selected and supported. The charts keep the previous results dimmed until the new results arrive. Returning to a previously sampled combination restores its results and resumes sampling from where it stopped. Every selection uses the same seed sequence (common random numbers), and a run's parameter draw depends only on the experiment's seed and the run's position in the sequence, so differences you see between selections come from the parameters, not from sampling luck — while experiments with different seeds explore their own value sequences. -#### Optimizing a sweep +### Optimizing a sweep + +Optimization is available when the host application provides an optimizer that runs in your browser. -The in-browser optimizer is experimental and off by default. Turn on **In-browser optimization** under Simulation in the [settings dialog](visual-settings.md#in-browser-optimization-experimental); the setting is offered only when the host application provides an optimizer that runs in your browser. Turning it off while a study runs cancels the study. +Selecting a swept interval exposes **Start optimizer immediately** in Metrics. This checkbox starts unchecked: **Create sweep** creates an idle sweep for manual exploration. Check it to show **Metrics & objective** and start optimization when you create the experiment. Add a metric, then choose **Maximize** or **Minimize** on its row. The first metric is the objective by default; select **Use as objective** on another metric to optimize it instead. The objective metric is highlighted in purple, with its controls in a compact shaded footer. All the experiment's metrics are still measured. **Optimization steps** appears below the list once a metric exists (30 by default, 1,000 at most). Each step computes eight runs at one point of the sweep before the optimizer reads the metric's value there, the mean over those runs on the last sampled frame; the line under the fields says so -- **30 steps · 8 runs each — the best point then refines to your run budget** -- or names the step budget the optimizer refuses (a run of more than 100,000 simulation steps, or steps × 8 runs × simulation steps over 5,000,000), and the footer stays disabled until it is met. A **No scenario** experiment optimizes too, over the generated parameters of its form values; only Constraints need a saved scenario. -With it on, the interval toggles of the Create Experiment drawer read **Optimize**, and the first one you flip adds an **Objective** section to the drawer, between Scenario and [Constraints](#constraints): the metric to optimize (one of the experiment's metrics, the first by default), **Maximize** or **Minimize**, and the number of steps to take (30 by default, 1,000 at most). Each step computes eight runs at one point of the sweep before the optimizer reads the metric's value there, the mean over those runs on the last sampled frame; the line under the fields says so -- **30 steps · 8 runs each — the best point then refines to your run budget** -- or names the step budget the optimizer refuses (a run of more than 100,000 simulation steps, or steps × 8 runs × simulation steps over 5,000,000), and the footer stays disabled until it is met. A **No scenario** experiment optimizes too, over the generated parameters of its form values; only Constraints need a saved scenario. +With **Start optimizer immediately** checked, the footer reads **Optimize**, then **Starting** while the scenario compiles and the study registers. The experiment then opens already optimizing: the **Parameters** card is purple, the header's status reads **Optimizing** and its progress bar counts the steps, the controls are locked and move by themselves to each point the optimizer tries, the line under the sliders reads **Testing step N** with the point's sampling progress (**· 5 / 8 runs**), and every point lands on the Surface as it computes; **Details → Active batches** lists the step's batch as **Step N**. The optimizer draws its first steps at random, about a third of the requested steps and at least 2 and at most 10, then proposes each further step from the results so far. Every step's runs use the sweep's common random numbers, so the differences between steps come from the parameters, not from sampling luck. Steps run one after another, and one study at a time: a study started while another runs waits for it, reading **Optimizing** at step 1 with no runs until its turn. Parameters you did not sweep hold at the values the experiment was created with. If the optimizer cannot start -- the optimizer disconnected, or a budget the form did not catch -- nothing is created: the panel stays open with the reason in its footer and every field as you left it. -The footer reads **Optimize**, then **Starting** while the scenario compiles and the study registers. The experiment then opens already optimizing: the **Parameters** card is purple, the header's status reads **Optimizing** and its progress bar counts the steps, the controls are locked and move by themselves to each point the optimizer tries, the line under the sliders reads **Following step N of M** with the point's runs as they stream (**— 5 of 8 runs**), and every point lands on the Surface as it computes; the **N computing** chip lists the step's batch as **Step N**. The optimizer draws its first steps at random, about a third of the requested steps and at least 2 and at most 10, then proposes each further step from the results so far. Every step's runs use the sweep's common random numbers, so the differences between steps come from the parameters, not from sampling luck. Steps run one after another, and one study at a time: a study started while another runs waits for it, reading **Optimizing** at step 1 with no runs until its turn. Parameters you did not sweep hold at the values the experiment was created with. If the optimizer cannot start -- the optimizer disconnected, or a budget the form did not catch -- nothing is created: the drawer stays open with the reason in its footer and every field as you left it. +To optimize an existing manual sweep, choose **Optimize** in the **Parameters** header. Select its metric, direction and step count, then choose **Start**. This uses the same step limits as creation. The controls work with the keyboard: Tab moves between fields, arrow keys change selections, and Escape closes the prompt. If startup fails, the prompt keeps your choices and shows the reason. -While the study drives the sweep the card's header carries one purple **Stop** button: it ends the search where it stands, and the point it was trying refines to your run budget; when the search finishes on its own the sliders settle on the best point found and that point refines the same way. Once the search settles, the sliders unlock and the line under the sliders keeps its outcome -- **Finished 30 steps · best step so far: step 12 (650.500)**, or **Stopped after 17 of 30 steps · …** -- with the parked point's sampling after it, until the experiment's removal. The value is named for what it is: the best of the steps tried, not a confirmed result at that configuration. From there the sweep is yours to explore by hand -- sliders, **Point** and **Range**, the Surface -- with the study's picture kept; the card offers nothing more, and a new search is a new experiment. **Cancel** in the drawer's footer stops the study as well as the sweep; **Remove** discards both. A study that fails reports its message in the line under the header, where the experiment's own error would read. The study appears nowhere else: the sweep's drawer is its home, and removing the experiment removes it. +While the study drives the sweep the card's header carries one purple **Stop** button: it ends the search where it stands, and the point it was trying refines to your run budget; when the search finishes on its own the sliders settle on the best point found and that point refines the same way. Once the search settles, the sliders unlock and the line under the sliders shows sampling progress while the selected point refines. Once sampling stops, it reads **Optimization complete**, **Optimization stopped**, or **Optimization failed**. The best value stays beside **Objective by step**, and **Details** keeps the full search summary. The value is named for what it is: the best of the steps tried, not a confirmed result at that configuration. From there you can explore by hand with the sliders and Surface, keeping the study's results visible, or choose **Optimize** to start another search. A successful start replaces the previous study's summary and charts; the sweep keeps its sampled points. **Cancel** in the panel's footer stops the study as well as the sweep; **Remove** discards both. A study that fails reports its message in the line under the header, where the experiment's own error would read. The study appears nowhere else: the sweep's panel is its home, and removing the experiment removes it. The first study in a browser downloads the Python runtime and the optimizer packages before its first step starts; the header reads **Optimizing** with no steps completed while that happens. Later studies reuse the browser's cache. Closing or reloading the page ends the study, and while one runs the browser asks you to confirm first; the study is gone on the next load, the sweep with it. The optimizer proposes with the same sampler, seed and start-up draws the [Petrinaut CLI](../../../@local/petrinaut-arch-docs/content/cli/usage-manual.mdx) uses, so a study's proposals match the CLI's step for step while the objective values it is told match. -An **Objective by step** strip sits under the sliders from the moment the drawer opens: every step's objective value as a purple dot over the step number, with the best so far as a line stepping through them, drawn as the steps land; the axis reaches to the steps asked for while the search runs and ends at the last step run once it settles. Infeasible draws carry no value and are left off the strip, and the best step so far is never one of them. Its title line names the metric and counts the steps, with the best value found; click the line to fold the chart away or bring it back. The strip stays once the search settles. A sweep created with **Create sweep** has no strip. +An **Objective by step** strip appears under the sliders when a study starts: every step's objective value as a purple dot over the step number, with the best so far as a line stepping through them, drawn as the steps land; the axis reaches to the steps asked for while the search runs and ends at the last step run once it settles. Infeasible draws carry no value and are left off the strip, and the best step so far is never one of them. Its title line names the metric and counts the steps, with the best value found; click the line to fold the chart away or bring it back. The strip stays once the search settles. A sweep has no strip until its first study starts. -The drawer's shape is fixed when the experiment is created, and it holds through running, stopped and failed studies: an experiment created with **Optimize** has, from its first frame, a headline over the header's columns, **Steps** and **Steps clear** columns after **Compute** (see [Reading the header](#reading-the-header)), the **Constraints** and **Sensitivity analysis** cards after the metric charts and the steps table under them (see [Metric charts](#metric-charts)), empty until the steps fill them; a sweep created with **Create sweep** has none of them. Nothing appears later, and nothing moves. +Once a study starts, the experiment shows its **Optimization steps** in the header, an objective chart under the parameter controls, the **Constraints** and **Sensitivity analysis** cards after the metric charts, and a steps table below them. These remain available after the search stops or fails. The detailed search summary and constraint totals are available in **Details**. -#### The surface view +### The surface view -A sweep with two or more swept parameters grows a **Surface** card under the **Parameters** card: a contour plot of one metric's final value over two parameters you pick, drawn from the points the sweep has computed. It starts empty. Every point you visit — by moving the sliders to a point, by clicking the plot, or through the optimizer — lands as a dot with its value, the field is interpolated between the dots once there are three, and the point being computed is a ring; its value joins the field once its batch completes. Points computed at other values of the parameters not shown are drawn too, projected onto the two you picked. The **X** and **Y** pickers sit in the row under the plot and the **Metric** picker in the row beneath them; every metric is measured at every point, so switching the shown metric repaints from what was already computed. The line under the card's title counts the points and what computes -- **computing the selected point** or **sampling across the selected ranges**, with the runs so far -- or, mid-drag, the values under the pointer. **The surface is itself a control**: click, or press and drag with a live crosshair and value readout, and on release every swept parameter collapses to a point -- the two shown at the place you released, the others at the middle of their current range -- which then computes. A dark ring marks where the navigator sits. While the optimizer drives the sweep the card is read-only: the plot only displays under a not-allowed cursor, the **X**, **Y** and **Metric** pickers lock, a purple **Read-only** mark sits beside them, and between two steps the line says the optimizer is choosing the next point. A cancelled sweep locks the same way, with the mark in grey. +A sweep with two or more swept parameters grows a **Surface** card under the **Parameters** card: a contour plot of one metric's final value over two parameters you pick, drawn from the points the sweep has computed. It starts empty. Every point you visit — by moving the sliders to a point, by clicking the plot, or through the optimizer — lands as a dot with its value, the field is interpolated between the dots once there are three, and the point being computed is a ring; its value joins the field once its batch completes. Points computed at other values of the parameters not shown are drawn too, projected onto the two you picked. The **X** and **Y** pickers sit in the row under the plot and the **Metric** picker in the row beneath them; every metric is measured at every point, so switching the shown metric repaints from what was already computed. The line under the card's title counts sampled points and indicates when sampling is active. During a drag it shows the values under the pointer. The help icon explains the shading and how to select a point. **The surface is itself a control**: click, or press and drag with a live crosshair and value readout, and on release every swept parameter collapses to a point -- the two shown at the place you released, the others at their current values -- which then computes. A dark ring marks where the navigator sits. While the optimizer drives the sweep the card is read-only: the plot only displays under a not-allowed cursor, the **X**, **Y** and **Metric** pickers lock, a purple **Read-only** mark sits beside them, and between two steps the line says the optimizer is choosing the next point. A cancelled sweep locks the same way, with the mark in grey. -The drawer arranges its parts by its width. The **Parameters** card spans the body under the header. Beneath it, at the drawer's full width, the **Surface** sits on the left and the metric cards on the right, two to a row, so two swept parameters and up to four metrics fit without scrolling; in a narrower drawer the metric cards come first, then **Surface**, so the charts you watch are at the top either way. A sweep with one swept parameter has no surface, and its cards take the whole width. Every card keeps a fixed height, and only the body scrolls, under the header. +The panel arranges its parts by its width. The **Parameters** card spans the body under the header. Beneath it, at the panel's full width, the **Surface** sits on the left and the metric cards on the right, two to a row, so two swept parameters and up to four metrics fit without scrolling; in a narrower panel the metric cards come first, then **Surface**, so the charts you watch are at the top either way. A sweep with one swept parameter has no surface, and its cards take the whole width. Every card keeps a fixed height, and only the body scrolls, under the header. -### Compute backend (experimental) +### Compute backend -Experiments run on the CPU unless you ask for the GPU. Switch on **WebGPU** under **Settings → Simulation**, and the Create Experiment drawer gains a **Run on GPU** switch. Running on your graphics hardware is dramatically faster — a 4000-run experiment that takes six seconds on the CPU finishes in a few milliseconds. +Experiments run on the CPU unless you ask for the GPU. When your browser supports WebGPU, the Create Experiment panel offers a **Run on GPU** switch. Running on your graphics hardware is dramatically faster — a 4000-run experiment that takes six seconds on the CPU finishes in a few milliseconds. The choice is per experiment, not global, so a GPU experiment and a CPU experiment can run side by side — useful for comparing the two on the same model. Each gets its own GPU device, so nothing is shared between them. @@ -147,34 +164,23 @@ Run count has no ceiling of its own: runs beyond what your GPU can hold at once Two things to know before comparing results: -- **The same seed gives different numbers on the two backends.** They deliberately use different random number generators, so the trajectories differ while the distributions agree. On the built-in SIR example the two backends' mean token counts agree to within half a percent. The badge in each experiment's header records which backend ran it, so results stay attributable after the fact. +- **The same seed gives different numbers on the two backends.** They deliberately use different random number generators, so the trajectories differ while the distributions agree. On the built-in SIR example the two backends' mean token counts agree to within half a percent. The badge in each experiment's **Details** records which backend ran it, so results stay attributable after the fact. - Continuous dynamics are integrated with a **more accurate method** (Runge-Kutta 4) than the CPU's, so a model with differential equations may show slightly different — better — values, not just different noise. - The GPU steps every run to the configured max time, while the CPU stops a run as soon as it can no longer fire anything. So a net that finishes early reports a **higher frame count and simulated time** on the GPU for the same results. Nothing is wrong with either; they just stop counting at different points. ### Reading the header -Open an experiment's drawer and its header names the experiment in one line: the name, the scenario (or **Default scenario**) and the run count, for example **SIR transmission sweep · Seasonal Flu · 100 runs**. Beneath it, a strip of labelled columns divided by hairlines, always on one line: in a narrow drawer the labels become tooltips and the columns read as chips, **Runs** and **Selection** shorten to their counts, and whatever still does not fit scrolls sideways under a fade at the edge. +The results header shows the experiment's name and status. Plain experiments show **Completed runs**, such as **640 / 1,000 runs**. Optimized sweeps show **Optimization steps**, such as **4 / 30 steps**. Failed runs appear beside the progress count when any occur. An idle sweep reads **Ready**, and sampling progress appears beside its parameter controls. -| Column | Meaning | -| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Status** | One of the statuses above, as a pill with a coloured dot. | -| **Runs** | Plain experiments: how many runs are in flight, and how many have finished. A sweep shows **Selection** in its place. | -| **Selection** | Sweeps, in place of **Runs**: the selected combination's runs sampled over the run budget. | -| **Errors** | How many individual runs errored. An experiment can complete with some runs errored. | -| **Time** | Simulated time reached, against the configured maximum. This is model time, not clock time. A sweep that has computed nothing reads `0`. | -| **Elapsed** | Plain experiments only: clock time the experiment has been simulating; it stops with the experiment and holds the total it took. A sweep never finishes, so it has no clock. | -| **Activity** | The **N computing** chip: how many batches run right now, **0 computing** when nothing does. Click it while something runs to list them. | -| **Compute** | Whether the run uses the **CPU** or the **GPU**. Hover it for detail; on a CPU-backed experiment that asked for the GPU, it names the requirement the net did not meet. | -| **Steps** | Sweeps created with **Optimize**: the steps finished over the steps requested, with the runs per step, **4 / 30 · 8 runs each**; the count alone in a narrow drawer. | -| **Steps clear** | Sweeps with a study over a sweep with [constraints](#constraints): the steps clear over the steps that simulated, **3 / 4 · 75%**. | +Click **Details** for the scenario, requested runs or sampling limit, runs sampled, errors, simulation time, elapsed time for plain experiments, and compute backend. A sweep's **Sampling limit** is the maximum number of runs for the selected parameter values. For example, **1,000 runs** means the selection can refine up to that many samples. Moving a control starts sampling the new selection. -A progress bar runs along the header's bottom edge: the selected combination's runs for a sweep (the study's steps while one drives it), simulated time otherwise. If the experiment failed, the error reads in the line under the header; so does the error of a study that failed while driving a sweep. +For optimized sweeps, **Details** also shows the steps finished, runs per step, constraint pass counts, best step, and how the search ended. While optimization runs, a chip describes recent progress: **Still improving**, **Converging**, or **Too early to say**. The assessment uses a window of one tenth of the requested steps, with a minimum of five completed steps. -For a sweep created with **Optimize**, the title line also carries the study's headline at its right: **Starting · no best step yet** before the first step, **Step 5 of 30 · best step so far: step 2 (650.500)** while it runs, then **Finished 30 steps · …**, **Stopped after 17 of 30 steps · …** or **Failed after …**. While it runs, a chip beside the line says whether the study is still finding better steps: **Still improving** when the best moved within the last few completed steps (a tenth of the requested steps, five at least), **Converging** when that many steps passed without a better one, and **Too early to say** before one such window has completed. The chip's place is reserved, so nothing moves when it appears or goes. +The **Compute** badge in Details shows **CPU** or **GPU**. Hover it for more information, including why a requested GPU backend fell back to the CPU. While batches run, **Active batches** opens their individual progress. -Once the drawer's body has scrolled, the header condenses to one line, with the columns folded in as compact chips beside the title, the compute badge and the computing chip still among them; move the pointer over it, or Tab onto one of its controls, and it grows back. Nothing in the header moves when a status changes, a count goes to zero or a number grows a digit: every column is as wide as its widest value, and every card in the body keeps its height. +The bar along the header's bottom edge follows sampling progress for a sweep, optimization steps while a study drives it, and simulation time for a plain experiment. Errors appear directly below the header. Scrolling the results condenses the header to one line; hovering over it or focusing a control expands it again. -**Elapsed** and **Duration** measure simulating only. Compiling the net's user code and starting the workers (or acquiring the GPU device and compiling the shader) happens before the clock starts, so the number is comparable between the two backends. An experiment that fails before it starts simulating shows `—` rather than a duration. +**Simulation time** is time within the model. **Elapsed time** and **Duration** measure wall-clock time spent simulating. Compilation and worker startup happen before that clock starts. An experiment that fails before simulation begins shows `—` for its elapsed time. ### Metric charts @@ -189,40 +195,40 @@ Click (or drag across) a timeline chart to inspect single time steps — a popov #### The study's cards -For a sweep created with **Optimize**, two more cards follow the metric charts in the same grid, at the same height, from the moment the drawer opens. +Once a study starts, two more cards follow the metric charts in the same grid, at the same height. + +- The **Constraints** card, only for a sweep with [constraints](#constraints). Its headline is the steps **clear** across the study over the steps that simulated, `14 / 20 · 70%`, with the pass threshold and the infeasible draws counted in the line under the title, **pass threshold 95% (alpha 0.05) · 2 infeasible draws**. Beneath it, one line gives the latest step's verdict -- **Clear**, **Limited · 6 / 8 runs passed · 75% · State constraint 1**, or **Infeasible: Parameter constraint 1** -- and one bar per state constraint shows the share of steps it passed, with a dashed mark at the threshold. The same count is available in **Details** as **Steps clear**. A step stopped mid-flight, or pruned because the sliders moved on, carries no verdict and counts in neither number. +- The **Sensitivity analysis** card lists the swept parameters by descending **Share** with a bar for how much each one matters for reaching the best steps and a **Share** percentage per parameter; parameters without an estimate follow the estimated ones. Tied shares keep the scenario's order. The estimate is Optuna's PED-ANOVA: it takes the best tenth of the completed steps and measures how concentrated each parameter's values are there relative to its whole range, a relative importance that sums to 100% rather than a share of the objective's variance. It is computed by the optimizer running in your browser once the study is over, and again every few steps while a long study runs (every tenth step, or every twentieth of the requested steps when that is more) once it is past the floor. The line under the title says **Based on N completed steps**, or **No estimate yet** while waiting for the first estimate. The help icon explains the method and recommended step count. Below the floor, 50 completed steps for a study of under 100 steps and 100 otherwise, the card is muted, the bars fade and an available estimate reads **Preliminary · N completed steps**: a confident estimate over a handful of steps would mislead, and at the default 30 steps the card stays muted. A **Correlation** column beside the bars gives each parameter's signed correlation with the objective over the completed steps (`+0.34`, `−0.12`), computed from the steps themselves, so it is there from the third completed step whatever the floor. Before the first estimate the rows show a dash. A study that optimizes a single parameter has nothing to rank it against: its line says **Correlation only · one parameter**, the card is never muted, and only the correlation column carries information. -- The **Constraints** card, only for a sweep with [constraints](#constraints). Its headline is the steps **clear** across the study over the steps that simulated, `14 / 20 · 70%`, with the pass threshold and the infeasible draws counted in the line under the title, **pass threshold 95% (alpha 0.05) · 2 infeasible draws**. Beneath it, one line gives the latest step's verdict -- **Clear**, **Limited · 6 / 8 runs passed · 75% · State constraint 1**, or **Infeasible: Parameter constraint 1** -- and one bar per state constraint shows the share of steps it passed, with a dashed mark at the threshold. The same headline sits in the header's strip as **Steps clear**. A step stopped mid-flight, or pruned because the sliders moved on, carries no verdict and counts in neither number. -- The **Sensitivity analysis** card lists the swept parameters in the scenario's order with a bar for how much each one matters for reaching the best steps and a **Share** percentage per parameter; the rows keep their places as estimates land. The estimate is Optuna's PED-ANOVA: it takes the best tenth of the completed steps and measures how concentrated each parameter's values are there relative to its whole range, a relative importance that sums to 100% rather than a share of the objective's variance. It is computed by the optimizer running in your browser once the study is over, and again every few steps while a long study runs (every tenth step, or every twentieth of the requested steps when that is more) once it is past the floor. The line under the title names the statistic and says how many completed steps it is fitted on. Below the floor, 50 completed steps for a study of under 100 steps and 100 otherwise, the card is muted, the bars fade and the line says **below the N-step floor, treat as a hint**, N being the floor just named: a confident estimate over a handful of steps would mislead, and at the default 30 steps the card stays muted. A **Correlation** column beside the bars gives each parameter's signed correlation with the objective over the completed steps (`+0.34`, `−0.12`), computed from the steps themselves, so it is there from the third completed step whatever the floor. Before the first estimate the rows show a dash. A study that optimizes a single parameter has nothing to rank it against: its line says **PED-ANOVA ranks two or more parameters**, the card is never muted, and only the correlation column carries information. +Under both columns, at the panel's full width, the **steps table** lists the study's steps newest first, each with its parameters, objective value and a state mark (complete, pruned or failed), the best step starred and tinted. It keeps a fixed height and scrolls on its own, and a long study shows its newest 200 steps while the header keeps the total and **Details** keeps the best step. A sweep with constraints adds a **Runs passed** column (`52 / 60 · 87%`, the constraint with the fewest passing runs when there are several) and greys the rows of infeasible steps, their mark reading **Infeasible:** and the constraint's name. -Under both columns, at the drawer's full width, the **steps table** lists the study's steps newest first, each with its parameters, objective value and a state mark (complete, pruned or failed), the best step starred and tinted. It keeps a fixed height and scrolls on its own, and a long study shows its newest 200 steps while the header keeps the totals and the best. A sweep with constraints adds a **Runs passed** column (`52 / 60 · 87%`, the constraint with the fewest passing runs when there are several) and greys the rows of infeasible steps, their mark reading **Infeasible:** and the constraint's name. +### Panel and fullscreen views + +Experiment creation and results open in a [panel beside the main view](simulation-panels.md). Expand it to fullscreen for more space; form edits and chart choices stay in place. + +Experiments belong to the current session. Reloading a link after that session has ended shows an unavailable message; it does not rerun the experiment. ### Actions -In the experiment's view drawer (open it from the list, where the first click selects a row and a click on the selected row or Enter opens it, or via any experiment in the top-bar **Active experiments** popover): +In the experiment's view panel (open it with a single click in the list, with **Up** or **Down** while browsing the list, or via any experiment in the top-bar **Active experiments** popover): - **Cancel** -- stops the experiment. Offered while it is initializing or running, and while a study drives a sweep, which it stops too. Once a sweep is cancelled its sliders and its surface lock; a selection that failed locks nothing, and the next selection computes normally. - **Remove** -- deletes the record and disposes the experiment's workers (and, for a sweep, its study). It sits at the left edge of the footer. -- **Close** -- closes the drawer without affecting the experiment. - -There is no built-in restart action -- to re-run with the same configuration, **Create** a new experiment with the same settings. +- **Close** -- closes the panel without affecting the experiment. -Opening and closing an existing experiment participates in Browser Back / -Forward history on hosts with app navigation enabled. Experiment records and -results remain session data: browser navigation can reopen a record while the -current Petrinaut session is mounted, but reloading a copied experiment URL -does not recreate the run. +To repeat a completed non-sweep experiment, **Create** a new experiment with the same settings. A sweep can start another optimizer search from its **Parameters** card. A confirmation prompt blocks browser/tab close while any experiment is initializing or running. ### Notifications -The **N computing** chip in the header's **Activity** column counts the batches running right now — a sweep pipelines the selection's batches two deep — and clicking it opens a compact list with each batch's label (**Selection**, or **Step N** while a study drives the sweep) and progress; it reads **0 computing** while nothing runs, and the list closes with its last batch. +In **Details**, **Active batches** counts the batches running and opens a list with each batch's label (**Selection**, or **Step N** during optimization) and progress. It appears while batches are active. -A small toast appears when an experiment **completes** or **errors**, even if its drawer isn't open. The top-bar **Active experiments** popover (see below) lets you jump to any in-flight experiment from anywhere in the app. +A small toast appears when an experiment **completes** or **errors**, even if its panel isn't open. The top-bar **Active experiments** popover (see below) lets you jump to any in-flight experiment from anywhere in the app. ## Active experiments popover -When any experiment is **initializing** or **running**, the top bar shows an **Active experiments** flask icon with a count (e.g. "2 active"). Click it for a popover listing each in-flight experiment with its scenario, progress, status, and a time progress bar. Clicking a row jumps directly to Simulate mode, the Experiments tab, and that experiment's drawer. +When any experiment is **initializing** or **running**, the top bar shows an **Active experiments** flask icon with a count (e.g. "2 active"). Click it for a popover listing each in-flight experiment with its scenario, progress, status, and a time progress bar. Clicking a row jumps directly to Simulate mode, the Experiments tab, and that experiment's panel. The popover hides itself again once nothing is in flight. @@ -237,7 +243,6 @@ You can press Play in Edit mode while experiments are running in the background, Changing the net while an experiment is running does **not** retroactively affect that experiment -- it captured its model snapshot when you pressed Run. -A parameter sweep is also where Petrinaut searches parameters: created with -**Optimize**, it searches its intervals with the in-browser optimizer through -its own compute, from the moment it starts (see [Optimizing a -sweep](#optimizing-a-sweep)). There is no separate optimization workflow. +A parameter sweep is also where Petrinaut searches parameters. Start its +in-browser optimizer during creation or later from **Parameters → Optimize** +(see [Optimizing a sweep](#optimizing-a-sweep)). diff --git a/libs/@hashintel/petrinaut/docs/petri-net-extensions.md b/libs/@hashintel/petrinaut/docs/petri-net-extensions.md index 148d3c8d7dd..835cf00373b 100644 --- a/libs/@hashintel/petrinaut/docs/petri-net-extensions.md +++ b/libs/@hashintel/petrinaut/docs/petri-net-extensions.md @@ -73,7 +73,7 @@ Differential equations define how token data evolves continuously over time. The 1. Create a differential equation in the **Differential Equations** group (left sidebar). 2. Give it a name and associate it with a **type** (the equation applies to tokens of that type). -3. Select a place, enable **Dynamics**, and choose an equation that matches the type assigned to the place. +3. Select a place, enable **Dynamics**, and choose an equation that matches the type assigned to the place. Click **View equation** below the dropdown to open its definition. **Function body:** @@ -93,7 +93,7 @@ The code is a plain function body ending in `return`, with `tokens` (the current A visualizer renders a custom view of a place's tokens during simulation. It is a React component that returns JSX (SVG is recommended). -**To enable:** select a place, then toggle **Visualizer** in its properties. A code editor opens. +**To enable:** select a place, then turn on the **Visualizer** switch in its properties. Click the **Visualizer** header to expand its code editor. ```tsx export default Visualization(({ tokens, parameters }) => { diff --git a/libs/@hashintel/petrinaut/docs/scenarios.md b/libs/@hashintel/petrinaut/docs/scenarios.md index b45f9146f66..bbcbba8433f 100644 --- a/libs/@hashintel/petrinaut/docs/scenarios.md +++ b/libs/@hashintel/petrinaut/docs/scenarios.md @@ -28,14 +28,18 @@ You will need scenarios when you want to: ## Creating a scenario 1. Switch to **Simulate** mode and open the **Scenarios** tab. -2. Click **Create**. The Create Scenario drawer opens. +2. Click **Create**. The Create Scenario panel opens. 3. Fill in **Scenario name** (required, unique among scenarios) and an optional description. 4. Add **Variables** -- one per value you want to drive from a single number, written `scenario.` in every expression below. Turn **Scenario Parameter** on to expose a Variable as a tunable parameter of the saved scenario: it needs a snake_case name, a constant expression as its default, and a value between 0 and 1 for a ratio. 5. Fill in **Parameters** -- an expression per net-level parameter whose default you want to override; the `default` tag marks the untouched ones. 6. Configure **Initial state** -- a count expression per untyped place, rows of cells per typed place (a Dynamic row builds many tokens from one count). See [Ad-hoc Scenarios](ad-hoc-scenarios.md#the-form) for the form itself. 7. Click **Create**. It is disabled while the name or any value has an error -- hover it to read the first. -The view drawer opens from the Scenarios list, which works like the other Simulate-mode lists: the first click selects a row, and a click on the selected row (or Enter) opens it. The list is a single Tab stop whose rows the arrow keys walk. The drawer shows the same form populated with the existing values, with **Close** and **Save** buttons. +The view panel opens from the Scenarios list with a single click. The list is a single Tab stop: **Up** and **Down** select and open the previous or next scenario while keeping focus in the list. The panel shows the same form populated with the existing values, with **Close** and **Save** buttons. Save your edits before selecting another scenario to keep them. + +## Panel and fullscreen views + +Scenario creation and editing open in a [panel beside the main view](simulation-panels.md). Expand it to fullscreen for more space; your unsaved edits stay in place. ## Expression language @@ -53,7 +57,7 @@ The subset is strict about booleans and equality: conditions and `&&`/`||` take ## Scenarios stored as code -Net files, the AI assistant and earlier versions of Petrinaut may store a scenario's initial state per place (one expression or one token spreadsheet per place) or as a single code block. Both run unchanged, and both preview as computed rows in Simulation Settings and the experiment drawer. Editing opens each in the form: a per-place scenario opens converted -- its parameters as exposed Variables, its expressions and rows as the form's blocks -- and saving stores it in the form's format; a code scenario opens with its name, description, Variables and Parameters editable and its code shown read-only in the Initial state slot -- edit its values here, change the code from the AI assistant or the net file, or recreate the scenario from the form (a Dynamic row builds many tokens from one count). A code scenario stores no form entries, only its scenario parameters: every Variable must be marked **Scenario Parameter** (the form refuses to save one that is not), and each is kept as its computed default. The code is a function body that returns an object keyed by **place name** -- a number for an untyped place (rounded, clamped to `>= 0`), an array of token objects for a typed one -- with `parameters`, `scenario` and `range` in scope; a key that is not a place name is a compile error, so a typo'd name fails the scenario instead of being silently ignored: +Net files, the AI assistant and earlier versions of Petrinaut may store a scenario's initial state per place (one expression or one token spreadsheet per place) or as a single code block. Both run unchanged, and both preview as computed rows in Simulation Settings and the experiment panel. Editing opens each in the form: a per-place scenario opens converted -- its parameters as exposed Variables, its expressions and rows as the form's blocks -- and saving stores it in the form's format; a code scenario opens with its name, description, Variables and Parameters editable and its code shown read-only in the Initial state slot -- edit its values here, change the code from the AI assistant or the net file, or recreate the scenario from the form (a Dynamic row builds many tokens from one count). A code scenario stores no form entries, only its scenario parameters: every Variable must be marked **Scenario Parameter** (the form refuses to save one that is not), and each is kept as its computed default. The code is a function body that returns an object keyed by **place name** -- a number for an untyped place (rounded, clamped to `>= 0`), an array of token objects for a typed one -- with `parameters`, `scenario` and `range` in scope; a key that is not a place name is a compile error, so a typo'd name fails the scenario instead of being silently ignored: ```ts return { diff --git a/libs/@hashintel/petrinaut/docs/simulation-panels.md b/libs/@hashintel/petrinaut/docs/simulation-panels.md new file mode 100644 index 00000000000..572f7795c71 --- /dev/null +++ b/libs/@hashintel/petrinaut/docs/simulation-panels.md @@ -0,0 +1,29 @@ +# Simulation panels + +Experiment creation, experiment results, and scenario creation and editing open in a panel beside the main view. The list or canvas stays visible and usable alongside it. + +## Select and resize + +Click an experiment or scenario once to open its panel. The selected row has a shaded background and a dark left edge. With focus in the list, press **Up** or **Down** to select and open the previous or next row. Focus stays in the list so you can keep browsing with the keyboard. + +Drag the divider between the list and the panel to adjust their widths. The panel keeps your chosen width when you return from fullscreen. If the window or docked assistant leaves less room, the panel fits the available space while keeping part of the list visible. + +## Expand and return + +Click **Expand to fullscreen** in the panel header to cover the list while keeping the vertical tabs visible. The header shows the tab and resource names, such as **Scenarios › Mars Orbit**. Click **Show as panel** to return to the side-by-side layout. The same form or results stay open: unsaved edits, expanded charts, and scroll position carry across the resize. + +Each tab remembers its selected resource and panel or fullscreen presentation during the session. Returning to a fullscreen resource shows it at full size immediately. Save scenario edits before switching tabs; unsaved form edits are not retained across tab changes. + +Expanding and collapsing animate the panel width and the tab name in the header when **Animations** is enabled in Settings. Both respect your system's reduced-motion preference. + +Both size controls and **Close panel** sit together on the header's right edge. Headers without a subtitle match the list header's height. Escape closes the panel while focus is inside it, unless an editor or menu handles Escape first. Closing an experiment's results leaves the experiment running. Save a scenario's edits before closing it or selecting another scenario to keep them. + +## Alongside the AI assistant + +A docked AI assistant takes space beside the main view and the simulation panel. Opening, closing, or resizing the assistant adjusts the space available to both. Fullscreen fills the main view beside the assistant. A floating assistant sits above the workspace. + +## Links and browser history + +On the Petrinaut website, the address includes the open experiment or scenario and whether it is fullscreen. Browser **Back** and **Forward** restore that view and its size. + +A scenario link requires the same model with that scenario saved. A link to a creation panel opens an empty form. Experiments belong to the current session; opening an experiment link after that session has ended shows an unavailable message and does not rerun it. diff --git a/libs/@hashintel/petrinaut/docs/simulation.md b/libs/@hashintel/petrinaut/docs/simulation.md index e607ac78cee..18b61365131 100644 --- a/libs/@hashintel/petrinaut/docs/simulation.md +++ b/libs/@hashintel/petrinaut/docs/simulation.md @@ -34,13 +34,13 @@ Quick-action buttons next to the picker let you edit the selected scenario, crea Override values for this run: - With **No scenario** selected: the form's **Parameters** table -- an expression per [net-level parameter](petri-net-extensions.md#global-parameters), the default shown with a `default` tag until you override it; expressions may read the Variables above as `scenario.`. -- With a scenario selected: the **scenario parameters** are shown instead, pre-filled with that scenario's defaults. Net-level parameter values are fixed by the scenario's [parameter overrides](scenarios.md#parameters) and are not editable here. Every selected scenario shows through the [ad-hoc form](ad-hoc-scenarios.md): its scenario parameters take value edits in the left column, and its parameter overrides and initial state sit read-only in the right one -- browsable with the same keyboard navigation, but only a scenario edit (the pencil next to the picker) changes them. A scenario saved from the ad-hoc form shows its definition; any other scenario shows a computed preview of the exact tokens the run will start with, recomputed as you change parameter values (very large places preview their first 100 rows). +- With a scenario selected: the **scenario parameters** are shown instead, pre-filled with that scenario's defaults. Net-level parameter values are fixed by the scenario's [parameter overrides](scenarios.md#parameters) and are not editable here. Every selected scenario shows through the [ad-hoc form](ad-hoc-scenarios.md): its scenario parameters take value edits in the left column, and its parameter overrides and initial state sit read-only in the right one -- browsable with the same keyboard navigation, but only a scenario edit (the pencil next to the picker) changes them. Every scenario shows computed parameter values and the exact tokens the run will start with, recomputed as you change scenario parameters. Select a read-only value to see its source expression in a floating cell over the selected value. The expression disappears when focus moves away. Very large places preview their first 100 rows. Changes here do not modify the parameter definition or the scenario -- they only apply to the simulation. Parameter values are locked while a simulation is running. Reset the simulation to change them. ### Time step (dt) -The time step in seconds per frame. Controls the resolution of ODE integration and how frequently transitions are evaluated. +Set the time step in seconds per frame using the **Time Step** input beside the scenario picker in Simulation Settings. It controls the resolution of ODE integration and how frequently transitions are evaluated. Hover the info icon beside its label for a short explanation. - **Smaller dt** -- finer approximation, but slower computation. - **Larger dt** -- faster, but less accurate for continuous dynamics. @@ -61,6 +61,12 @@ If there are unresolved error-severity [diagnostics](petri-net-extensions.md#dia simulation-settings +### Navigating Simulation Settings + +Use arrow keys to move between the scenario picker, its action buttons, the time-step field, and the tables below. Enter opens a picker or edits a selected table value. Text fields keep Left and Right for moving the caret until it reaches an edge. Tab follows the usual browser order. + +The uppercase section headers stack at the top as you scroll each column. Earlier headers fade slightly; click one to return to that section. Upcoming section headers stay at the bottom; click one to jump ahead. A soft fade marks the edge where content scrolls beneath the headers and clears when you return to the section's start. Informational tooltips are skipped by Tab and arrow-key navigation. Scrollbars overlay the content when you hover over a scrollable area, without shifting the columns. Initial state starts with the places marked **Default starting place** in their properties. Turn on the compact **Show all places** switch beside the Initial state title and info icon to inspect the rest. The switch appears only when the model contains other places. Left collapses an expanded place; Left again moves to the neighbouring focus group. + ## How a frame is computed Each simulation step proceeds in two phases: diff --git a/libs/@hashintel/petrinaut/docs/visual-settings.md b/libs/@hashintel/petrinaut/docs/visual-settings.md index f5f858be507..88ae09e5e7a 100644 --- a/libs/@hashintel/petrinaut/docs/visual-settings.md +++ b/libs/@hashintel/petrinaut/docs/visual-settings.md @@ -2,7 +2,7 @@ Open **User settings** from the menu in the top-left corner, press **Cmd+,** on macOS or **Ctrl+,** on Windows/Linux, or run **Open user settings** in the Command Palette. -Settings are available from every workspace tab, including **Simulate → Experiments**, **Actual**, and **Notebook** when those modes are available. The **gear icon** in the bottom-right viewport controls opens the **Viewport** section directly. +Settings are available in **Edit** (Canvas or Definitions), **Simulate**, and **Actual** when that mode is available. The **gear icon** in the bottom-right viewport controls opens the **Viewport** section directly. Use the vertical tabs to browse the sections. With a tab focused, use **Up/Down** to switch sections and **Home/End** to jump to the first or last section. **Right** moves into the section's settings, **Up/Down** moves between controls, and **Left** returns to the selected tab. Moving back into the same section restores the last focused control. **Tab** and **Shift+Tab** also reach every control. Use **Space** to toggle a switch and **Enter** to open a dropdown; an open dropdown uses its own arrow keys. Press **Escape**, click the close button, or click outside the dialog to close it. @@ -10,14 +10,13 @@ Scroll within a section to reach its remaining settings. The heading, section ta Drag the heading or empty space across the top of the settings window to move it, including while its content is scrolled. Drag any edge or corner to resize it. The opposite edge stays fixed, and the window remains inside the viewport. Switching sections preserves its size and position; reopening returns it to its initial size and position. The canvas keeps its normal brightness while the dialog is open. Text selection is disabled throughout the dialog. -Changes apply immediately and are saved as your preferences across nets. On hosts with app navigation enabled, opening or closing the dialog is part of Browser Back / Forward history. Switching settings sections updates the current history entry. Browser navigation leaves saved preferences in place. +Changes apply immediately and are saved as your preferences across nets. Defaults apply only to settings you have not chosen; your saved preferences take precedence. On hosts with app navigation enabled, opening or closing the dialog is part of Browser Back / Forward history. Switching settings sections updates the current history entry. Browser navigation leaves saved preferences in place. -| Section | Settings | -| -------------- | -------------------------------------------------------------------- | -| **General** | Animations, panel loading, and the welcome guide. | -| **Viewport** | Minimap, compact nodes, arc rendering, grid snapping, and selection. | -| **Simulation** | Experimental compute, parameter sweeps, and optimization options. | -| **Labs** | Experimental modeling views, code layouts, and developer tools. | +| Section | Settings | +| ------------ | -------------------------------------------------------------------- | +| **General** | Animations, panel loading, and the welcome guide. | +| **Viewport** | Minimap, compact nodes, arc rendering, grid snapping, and selection. | +| **Labs** | Experimental modeling views, code layouts, and developer tools. | ## General @@ -163,26 +162,8 @@ Controls selection box behavior in [Select mode](drawing-a-net.md#pan-and-select - **Enabled** -- nodes that are only partially inside the selection box are selected. - **Disabled** -- nodes must be fully enclosed to be selected. -## Simulation - -### WebGPU (experimental) - -Off by default. Offers a GPU option when creating an experiment; each experiment then chooses its own backend. See [Compute backend](experiments.md#compute-backend-experimental). - -### Parameter sweeps (experimental) - -Off by default. Adds an interval toggle to every numeric value of the experiment form -- **Sweep**, or **Optimize** when In-browser optimization is on -- so an experiment explores an interval instead of one value. See [Parameter sweeps](experiments.md#parameter-sweeps). - -### In-browser optimization (experimental) - -Shown only when the host application provides an optimizer that runs in your browser. Off by default. On, the experiment form's interval toggles read **Optimize**: creating the experiment starts a study over the selected intervals, with an **Objective** and **Constraints** chosen in the form. Off, the toggles read **Sweep** and the sweep waits for your selection; any running in-browser optimization is cancelled. See [Optimizing a sweep](experiments.md#optimizing-a-sweep). - ## Labs -### Notebook view (experimental) - -Add a **Notebook** tab to explore the net as expandable, read-only cells. Off by default. - ### Net Components (experimental) Enable subnet definitions and component instances for hierarchical nets. This option appears when the net supports subnets. Off by default. diff --git a/libs/@hashintel/petrinaut/src/react/experiments/context.ts b/libs/@hashintel/petrinaut/src/react/experiments/context.ts index 76873f5a1c0..c91032abaee 100644 --- a/libs/@hashintel/petrinaut/src/react/experiments/context.ts +++ b/libs/@hashintel/petrinaut/src/react/experiments/context.ts @@ -120,6 +120,8 @@ export type CreateExperimentOptions = { export type ExperimentRecord = { id: string; + /** The model snapshot used by the runs and any optimization started later. */ + definition?: SDCPN; /** Compute controls are held until a host request captures its result. */ requestActive?: boolean; name: string; diff --git a/libs/@hashintel/petrinaut/src/react/experiments/parameter-grid.test.ts b/libs/@hashintel/petrinaut/src/react/experiments/parameter-grid.test.ts index 79dacba2858..4c58bea633d 100644 --- a/libs/@hashintel/petrinaut/src/react/experiments/parameter-grid.test.ts +++ b/libs/@hashintel/petrinaut/src/react/experiments/parameter-grid.test.ts @@ -9,6 +9,7 @@ import { getNextRunTarget, mergeMetricFramesAcrossCells, normalizeSweepSelection, + pointSweepSelection, SWEEP_AXIS_STEPS, sweepRunFraction, } from "./parameter-grid"; @@ -198,6 +199,22 @@ describe("selections and regions", () => { }), ).toEqual({ x: { from: 0, to: 4 }, y: { from: 1, to: 1 } }); }); + + it("selects midpoint values for missing axes and retains selected points", () => { + expect(pointSweepSelection(axes, { x: { from: 3, to: 3 } })).toEqual({ + x: { from: 3, to: 3 }, + y: { from: 1, to: 1 }, + }); + }); + + it("collapses ranges to their nearest valid midpoint", () => { + expect( + pointSweepSelection(axes, { + x: { from: 4, to: 1 }, + y: { from: -3, to: 9 }, + }), + ).toEqual({ x: { from: 3, to: 3 }, y: { from: 1, to: 1 } }); + }); }); describe("sweepRunFraction", () => { diff --git a/libs/@hashintel/petrinaut/src/react/experiments/parameter-grid.ts b/libs/@hashintel/petrinaut/src/react/experiments/parameter-grid.ts index 46eee576cb3..606e8c6fa4e 100644 --- a/libs/@hashintel/petrinaut/src/react/experiments/parameter-grid.ts +++ b/libs/@hashintel/petrinaut/src/react/experiments/parameter-grid.ts @@ -69,7 +69,7 @@ export type SweepAxisSelection = { from: number; to: number }; /** * The navigator's selection: an inclusive position range per swept - * parameter. The default selection spans every axis whole. + * parameter. */ export type SweepSelection = Readonly>; @@ -291,6 +291,19 @@ export function normalizeSweepSelection( ); } +export const pointSweepSelection = ( + axes: readonly ExperimentParameterAxis[], + selection: SweepSelection, +): SweepSelection => { + const normalized = normalizeSweepSelection(axes, selection); + return Object.fromEntries( + axes.map((axis) => { + const position = Math.round(selectionMidpoint(normalized, axis)); + return [axis.identifier, { from: position, to: position }]; + }), + ); +}; + // One prime base per swept axis: two axes sharing a base would draw along a // diagonal. A sweep can range every scenario parameter, so the list is long. const HALTON_BASES = [ diff --git a/libs/@hashintel/petrinaut/src/react/experiments/provider.test.tsx b/libs/@hashintel/petrinaut/src/react/experiments/provider.test.tsx index 6d148ff4692..404c5535e53 100644 --- a/libs/@hashintel/petrinaut/src/react/experiments/provider.test.tsx +++ b/libs/@hashintel/petrinaut/src/react/experiments/provider.test.tsx @@ -472,7 +472,7 @@ describe("ExperimentsProvider", () => { getValue().setSweepSelection(experimentId, selection); }); expect(getValue().experiments[0]?.sweep?.selection).toEqual({ - rate: { from: 0, to: 50 }, + rate: { from: 25, to: 25 }, }); expect(worker.sent).toHaveLength(0); await act(async () => { @@ -623,6 +623,7 @@ describe("ExperimentsProvider", () => { ]); expect(experiment).toMatchObject({ name: "Initializing experiment", + definition: EMPTY_SDCPN, status: "initializing", sweep: null, }); @@ -1711,7 +1712,7 @@ describe("ExperimentsProvider", () => { expect(experiment).toMatchObject({ status: "initializing", - sweep: { computing: false, selectionKey: "beta=0..50" }, + sweep: { computing: false, selectionKey: "beta=25" }, scenario: { id: "scenario" }, }); // The session took the move: a batch reached the worker, and the @@ -1763,7 +1764,7 @@ describe("ExperimentsProvider", () => { // Nothing computes until a point is selected. expect(getValue().selectedExperiment).toMatchObject({ status: "idle", - sweep: { computing: false, selectionKey: "beta=0..50" }, + sweep: { computing: false, selectionKey: "beta=25" }, }); await act(async () => { diff --git a/libs/@hashintel/petrinaut/src/react/experiments/provider.tsx b/libs/@hashintel/petrinaut/src/react/experiments/provider.tsx index 9614a615de1..db966da851a 100644 --- a/libs/@hashintel/petrinaut/src/react/experiments/provider.tsx +++ b/libs/@hashintel/petrinaut/src/react/experiments/provider.tsx @@ -354,6 +354,7 @@ export const ExperimentsProvider: React.FC = ({ const experimentId = experiment.id; const session = createSweepSession({ axes, + initialSelection: experiment.sweep?.selection, runCount: experiment.runCount, seed: experiment.seed, // Nothing computes until a control moves or an optimizer navigates. @@ -496,6 +497,7 @@ export const ExperimentsProvider: React.FC = ({ fixedScenarioValues: compiled.fixedScenarioValues, scenario: compiled.scenario, }); + experiment.definition = experimentSdcpn; const ownership = options?.ownership; if (ownership) { experiment.requestActive = true; diff --git a/libs/@hashintel/petrinaut/src/react/experiments/provider/create-experiment.ts b/libs/@hashintel/petrinaut/src/react/experiments/provider/create-experiment.ts index 19361e16822..c10378a92cb 100644 --- a/libs/@hashintel/petrinaut/src/react/experiments/provider/create-experiment.ts +++ b/libs/@hashintel/petrinaut/src/react/experiments/provider/create-experiment.ts @@ -21,7 +21,7 @@ import { constraintIndicatorSpecs } from "../constraint-indicators"; import { buildAdHocSweepAxes, buildParameterAxis, - fullSweepSelection, + pointSweepSelection, } from "../parameter-grid"; import { sweepSelectionKey } from "../sweep-session"; @@ -367,7 +367,7 @@ export const compileExperimentScenario = async ({ const idleSweepState = ( axes: readonly ExperimentParameterAxis[], ): ExperimentSweepState => { - const selection = fullSweepSelection(axes); + const selection = pointSweepSelection(axes, {}); return { selection, selectionKey: sweepSelectionKey(axes, selection), diff --git a/libs/@hashintel/petrinaut/src/react/hooks/use-petrinaut-commands.test.tsx b/libs/@hashintel/petrinaut/src/react/hooks/use-petrinaut-commands.test.tsx index 9e2ca5d5bf7..5e59a18326d 100644 --- a/libs/@hashintel/petrinaut/src/react/hooks/use-petrinaut-commands.test.tsx +++ b/libs/@hashintel/petrinaut/src/react/hooks/use-petrinaut-commands.test.tsx @@ -40,6 +40,7 @@ const editorContextValue = ( globalMode, navigateTo: () => {}, setGlobalMode: () => {}, + setEditViewMode: () => {}, setEditionMode: () => {}, setAddComponentMode: () => {}, setCursorMode: () => {}, diff --git a/libs/@hashintel/petrinaut/src/react/hooks/use-petrinaut-mutations.test.tsx b/libs/@hashintel/petrinaut/src/react/hooks/use-petrinaut-mutations.test.tsx index 03edb94f52d..3f3f9f15483 100644 --- a/libs/@hashintel/petrinaut/src/react/hooks/use-petrinaut-mutations.test.tsx +++ b/libs/@hashintel/petrinaut/src/react/hooks/use-petrinaut-mutations.test.tsx @@ -43,6 +43,7 @@ const editorContextValue = ( globalMode, navigateTo: () => {}, setGlobalMode: () => {}, + setEditViewMode: () => {}, setEditionMode: () => {}, setAddComponentMode: () => {}, setCursorMode: () => {}, diff --git a/libs/@hashintel/petrinaut/src/react/index.ts b/libs/@hashintel/petrinaut/src/react/index.ts index 257c0f14a11..4159485c918 100644 --- a/libs/@hashintel/petrinaut/src/react/index.ts +++ b/libs/@hashintel/petrinaut/src/react/index.ts @@ -63,6 +63,7 @@ export type { // either gains a member. export type { EditorGlobalMode, + EditViewMode, SimulateViewMode, } from "./state/editor-context"; export { diff --git a/libs/@hashintel/petrinaut/src/react/navigation/index.test.tsx b/libs/@hashintel/petrinaut/src/react/navigation/index.test.tsx index 5f6129a882c..eadd2f1b5eb 100644 --- a/libs/@hashintel/petrinaut/src/react/navigation/index.test.tsx +++ b/libs/@hashintel/petrinaut/src/react/navigation/index.test.tsx @@ -21,6 +21,64 @@ import type { } from "."; describe("Petrinaut navigation", () => { + test("clears an expanded SubView when its item or subnet changes", () => { + const initialState: PetrinautNavigationState = { + ...defaultPetrinautNavigationState, + selection: [{ type: "transition", id: "collision" }], + expandedSubView: { + container: "transition-properties", + id: "transition-results", + }, + }; + const onNavigate = vi.fn(); + const Probe = () => { + const { navigate } = usePetrinautNavigation(); + return ( + <> + + + + ); + }; + render( + + + , + ); + fireEvent.click( + screen.getByRole("button", { name: "Select another transition" }), + ); + expect( + onNavigate.mock.calls.at(-1)?.[0](initialState).expandedSubView, + ).toBeNull(); + fireEvent.click(screen.getByRole("button", { name: "Open subnet" })); + expect( + onNavigate.mock.calls.at(-1)?.[0](initialState).expandedSubView, + ).toBeNull(); + }); + test("distinguishes settings sections and normalizes the default section", () => { const state = ( section?: "general" | "viewport", @@ -562,3 +620,47 @@ describe("Petrinaut navigation", () => { }); }); }); + +test.each([null, { type: "metric", id: "metric-a" }] as const)( + "clears fullscreen when leaving a panel for %j", + (resource) => { + const Probe = () => { + const { state, navigate } = usePetrinautNavigation(); + return ( + <> + + {state.simulatePresentation ?? "panel"} + + + + ); + }; + const view = render( + + + , + ); + expect(screen.getByLabelText("Presentation").textContent).toBe( + "fullscreen", + ); + fireEvent.click(screen.getByRole("button", { name: "Leave panel" })); + expect(screen.getByLabelText("Presentation").textContent).toBe("panel"); + view.unmount(); + }, +); diff --git a/libs/@hashintel/petrinaut/src/react/navigation/index.tsx b/libs/@hashintel/petrinaut/src/react/navigation/index.tsx index 9757d6bb29e..93c532d3dfd 100644 --- a/libs/@hashintel/petrinaut/src/react/navigation/index.tsx +++ b/libs/@hashintel/petrinaut/src/react/navigation/index.tsx @@ -19,6 +19,7 @@ import { ActualModeContext } from "../actual-mode-context"; import type { EditorGlobalMode, + EditViewMode, SimulateDrawerState, SimulateViewMode, } from "../state/editor-context"; @@ -29,11 +30,7 @@ export type PetrinautSimulateResource = | { type: "metric"; id: string } | { type: "experiment"; id: string }; -export type PetrinautSettingsSection = - | "general" - | "viewport" - | "simulation" - | "labs"; +export type PetrinautSettingsSection = "general" | "viewport" | "labs"; export type PetrinautNavigationOverlay = | { type: "user-settings"; section?: PetrinautSettingsSection } @@ -52,21 +49,27 @@ export type PetrinautNavigationOverlay = */ export type PetrinautNavigationState = { mode: EditorGlobalMode; + editView: EditViewMode; simulateView: SimulateViewMode; simulateResource: PetrinautSimulateResource | null; + /** Omission uses the panel presentation, including in existing host controllers. */ + simulatePresentation?: "panel" | "fullscreen"; scenarioId: string | null | undefined; subnetId: string | null; selection: readonly SelectionItem[]; + expandedSubView: { container: string; id: string } | null; overlay: PetrinautNavigationOverlay; }; export const defaultPetrinautNavigationState: PetrinautNavigationState = { mode: "edit", + editView: "canvas", simulateView: "experiments", simulateResource: null, scenarioId: undefined, subnetId: null, selection: [], + expandedSubView: null, overlay: null, }; @@ -74,11 +77,14 @@ export type PetrinautNavigationHistory = "push" | "replace"; export type PetrinautNavigationAction = | "mode" + | "edit-view" | "simulation-view" | "simulation-resource" + | "simulation-presentation" | "scenario" | "subnet" | "selection" + | "subview" | "overlay"; export type PetrinautNavigationIntent = @@ -171,12 +177,17 @@ export const petrinautNavigationStatesMatch = ( right: Readonly, ) => left.mode === right.mode && + left.editView === right.editView && left.simulateView === right.simulateView && left.simulateResource?.type === right.simulateResource?.type && left.simulateResource?.id === right.simulateResource?.id && + (left.simulatePresentation ?? "panel") === + (right.simulatePresentation ?? "panel") && left.scenarioId === right.scenarioId && left.subnetId === right.subnetId && selectionsMatch(left.selection, right.selection) && + left.expandedSubView?.container === right.expandedSubView?.container && + left.expandedSubView?.id === right.expandedSubView?.id && left.overlay?.type === right.overlay?.type && (left.overlay?.type !== "user-settings" || right.overlay?.type !== "user-settings" || @@ -189,10 +200,25 @@ const resolveNavigationUpdate = ( ): PetrinautNavigationState => { const updated = typeof update === "function" ? update(current) : { ...current, ...update }; + const selection = canonicalizeSelection(updated.selection); + const scopeChanged = + updated.subnetId !== current.subnetId || + !selectionsMatch(selection, current.selection); return { ...updated, - selection: canonicalizeSelection(updated.selection), + selection, + expandedSubView: + scopeChanged && updated.expandedSubView === current.expandedSubView + ? null + : updated.expandedSubView, + simulatePresentation: + updated.simulateResource?.type === "scenario" || + updated.simulateResource?.type === "experiment" || + updated.overlay?.type === "create-experiment" || + updated.overlay?.type === "create-scenario" + ? updated.simulatePresentation + : undefined, }; }; @@ -210,6 +236,32 @@ export const PetrinautNavigationProvider = ({ selection: canonicalizeSelection(initialState?.selection ?? []), })); const state = controller?.state ?? uncontrolledState; + const [simulationVisits, setSimulationVisits] = useState< + Partial< + Record< + SimulateViewMode, + Pick< + PetrinautNavigationState, + "simulateResource" | "simulatePresentation" + > + > + > + >({}); + const visit = simulationVisits[state.simulateView]; + if ( + visit === undefined || + visit.simulateResource?.type !== state.simulateResource?.type || + visit.simulateResource?.id !== state.simulateResource?.id || + visit.simulatePresentation !== state.simulatePresentation + ) { + setSimulationVisits({ + ...simulationVisits, + [state.simulateView]: { + simulateResource: state.simulateResource, + simulatePresentation: state.simulatePresentation, + }, + }); + } /** * React normally rerenders after navigation, but several UI libraries emit * related callbacks in the same event. Track the state those accepted @@ -250,7 +302,24 @@ export const PetrinautNavigationProvider = ({ ) => { const updater: PetrinautNavigationUpdater = ( current, - ) => resolveNavigationUpdate(current, update); + ) => { + const next = resolveNavigationUpdate(current, update); + if ( + intent.cause === "user" && + intent.action === "simulation-view" && + next.simulateView !== current.simulateView + ) { + return { + ...next, + simulateResource: + simulationVisits[next.simulateView]?.simulateResource ?? null, + simulatePresentation: + simulationVisits[next.simulateView]?.simulatePresentation, + overlay: null, + }; + } + return next; + }; const optimistic = optimisticRef.current; const current = optimistic?.preview ?? state; const preview = updater(current); diff --git a/libs/@hashintel/petrinaut/src/react/optimizations/provider.test.tsx b/libs/@hashintel/petrinaut/src/react/optimizations/provider.test.tsx index 82dfc5b0286..a3012086c2b 100644 --- a/libs/@hashintel/petrinaut/src/react/optimizations/provider.test.tsx +++ b/libs/@hashintel/petrinaut/src/react/optimizations/provider.test.tsx @@ -26,7 +26,6 @@ import { usePetrinautNavigation, } from "../navigation"; import { PetrinautOptimizationContext } from "../optimization-context"; -import { UserSettingsContext } from "../state/user-settings-context"; import { type OptimizationBest, OptimizationsContext, @@ -112,21 +111,6 @@ const CaptureNavigation = ({ return null; }; -/** Overrides the In-browser optimization setting below the default context. */ -const InBrowserOptimizationSetting = ({ - enabled, - children, -}: PropsWithChildren<{ enabled: boolean }>) => { - const value = use(UserSettingsContext); - return ( - - {children} - - ); -}; - /** Routes the provider's sweep navigations to a fake. */ const NavigateSweepOverride = ({ navigateSweep, @@ -151,7 +135,7 @@ const inertCapabilityMembers = { /** * A connected source whose runs stay quiet until aborted, counting connections - * and disposals so tests can observe what the setting gates. + * and disposals so tests can observe the connection lifecycle. */ const createQuietConnectedSource = () => { const calls = { connect: 0, dispose: 0 }; @@ -324,37 +308,33 @@ const createEvaluatingSource = ( const renderProvider = ({ source, navigateSweep = createNavigateSweep(), - enabled = true, }: { - source: PetrinautConnectedOptimization | PetrinautOptimization; + source: PetrinautConnectedOptimization | PetrinautOptimization | null; navigateSweep?: ExperimentsActionsValue["navigateSweep"]; - enabled?: boolean; }) => { let latest: OptimizationsContextValue | null = null; let navigationState: Readonly | null = null; - const tree = (isEnabled: boolean) => ( - - - - { - navigationState = value; - }} - /> - - - { - latest = value; - }} - /> - - - - - + const tree = (currentSource: typeof source) => ( + + + { + navigationState = value; + }} + /> + + + { + latest = value; + }} + /> + + + + ); - const { rerender, unmount } = render(tree(enabled)); + const { rerender, unmount } = render(tree(source)); return { getValue: () => { if (!latest) { @@ -368,7 +348,7 @@ const renderProvider = ({ } return navigationState; }, - setEnabled: (isEnabled: boolean) => rerender(tree(isEnabled)), + setSource: (currentSource: typeof source) => rerender(tree(currentSource)), unmount, }; }; @@ -379,14 +359,12 @@ afterEach(() => { }); describe("OptimizationsProvider and its source", () => { - it("treats a connected source as absent while In-browser optimization is off", async () => { - const { source, calls } = createQuietConnectedSource(); - const { getValue } = renderProvider({ source, enabled: false }); + it("reports optimization unavailable when the host provides no source", async () => { + const { getValue } = renderProvider({ source: null }); await expect( getValue().createOptimization(input, { sweep }), ).rejects.toThrow("Optimization is unavailable"); - expect(calls.connect).toBe(0); expect(getValue().optimizations).toHaveLength(0); }); @@ -398,7 +376,7 @@ describe("OptimizationsProvider and its source", () => { }, cancelOptimizationRun: () => Promise.resolve(), }; - const { getValue } = renderProvider({ source: capability, enabled: false }); + const { getValue } = renderProvider({ source: capability }); await expect( getValue().createOptimization(input, { sweep }), @@ -406,9 +384,11 @@ describe("OptimizationsProvider and its source", () => { expect(getValue().optimizations).toHaveLength(0); }); - it("connects and disposes a connected source as In-browser optimization is toggled, stopping the studies made through it", async () => { + it("connects on the first study and disposes when the host removes the source", async () => { const { source, calls } = createQuietConnectedSource(); - const { getValue, setEnabled } = renderProvider({ source }); + const { getValue, setSource } = renderProvider({ source }); + + expect(calls).toEqual({ connect: 0, dispose: 0 }); await act(async () => { await getValue().createOptimization(input, { sweep }); @@ -418,7 +398,7 @@ describe("OptimizationsProvider and its source", () => { ); expect(calls).toEqual({ connect: 1, dispose: 0 }); - setEnabled(false); + setSource(null); expect(calls).toEqual({ connect: 1, dispose: 1 }); await waitFor(() => expect(getValue().optimizations[0]?.status).toBe("cancelled"), @@ -427,7 +407,7 @@ describe("OptimizationsProvider and its source", () => { getValue().createOptimization(input, { sweep }), ).rejects.toThrow("Optimization is unavailable"); - setEnabled(true); + setSource(source); await act(async () => { await getValue().createOptimization(input, { sweep }); }); @@ -730,7 +710,7 @@ describe("OptimizationsProvider driving a sweep", () => { expect(getValue().optimizations[0]).toMatchObject({ status: "cancelled" }); }); - it("parks the sweep, uncapped, on the point it was trying when In-browser optimization is switched off mid-study", async () => { + it("parks the sweep, uncapped, when the host removes the optimizer mid-study", async () => { const { source } = createEvaluatingSource([0.05, 0.02]); let releaseStep: (cell: SweepVisitedCell | null) => void = () => {}; const navigateSweep = vi.fn( @@ -745,14 +725,14 @@ describe("OptimizationsProvider driving a sweep", () => { }) : Promise.resolve(null), ); - const { getValue, setEnabled } = renderProvider({ source, navigateSweep }); + const { getValue, setSource } = renderProvider({ source, navigateSweep }); await act(async () => { await getValue().createOptimization(input, { sweep }); }); await waitFor(() => expect(navigateSweep).toHaveBeenCalledTimes(1)); - act(() => setEnabled(false)); + act(() => setSource(null)); // The source is gone before the aborted step reports, so the study's own // cancel finds no evaluator: the sweep still parks, uncapped, on the point // the step was trying. diff --git a/libs/@hashintel/petrinaut/src/react/optimizations/provider.tsx b/libs/@hashintel/petrinaut/src/react/optimizations/provider.tsx index d6d8b29b105..fe7f2c917d1 100644 --- a/libs/@hashintel/petrinaut/src/react/optimizations/provider.tsx +++ b/libs/@hashintel/petrinaut/src/react/optimizations/provider.tsx @@ -22,6 +22,7 @@ import { ExperimentsActionsContext } from "../experiments/context"; import { errorMessage } from "../experiments/shared/error-message"; import { useBlockWindowClose } from "../hooks/use-block-window-close"; import { useLatest } from "../hooks/use-latest"; +import { PetrinautOptimizationContext } from "../optimization-context"; import { foldBestTrial, type OptimizationBest, @@ -35,7 +36,6 @@ import { createSweepTrialEvaluator, type SweepTrialEvaluator, } from "./provider/create-sweep-trial-evaluator"; -import { useOptimizationSource } from "./use-optimization-source"; import type { PropsWithChildren } from "react"; @@ -92,7 +92,7 @@ const connectOptimizationSource = ( }; export const OptimizationsProvider = ({ children }: PropsWithChildren) => { - const source = useOptimizationSource(); + const source = use(PetrinautOptimizationContext); const experimentsActionsRef = useLatest(use(ExperimentsActionsContext)); const connectionRef = useRef(null); const abortControllersRef = useRef(new Map()); diff --git a/libs/@hashintel/petrinaut/src/react/optimizations/use-optimization-source.ts b/libs/@hashintel/petrinaut/src/react/optimizations/use-optimization-source.ts deleted file mode 100644 index ed97406d52c..00000000000 --- a/libs/@hashintel/petrinaut/src/react/optimizations/use-optimization-source.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { use } from "react"; - -import { - isConnectedOptimization, - type PetrinautOptimizationSource, -} from "@hashintel/petrinaut-core/optimization"; - -import { PetrinautOptimizationContext } from "../optimization-context"; -import { UserSettingsContext } from "../state/user-settings-context"; - -/** - * The host's optimization source as the UI may use it. A remote capability - * passes through unchanged; a connected one counts only while the experimental - * In-browser optimization setting is on. `null` turns the experiment form's - * interval toggles back to Sweep, hides the Create Experiment drawer's - * Objective and Constraints sections, and nothing connects. - */ -export const useOptimizationSource = (): PetrinautOptimizationSource | null => { - const source = use(PetrinautOptimizationContext); - const { enableInBrowserOptimization } = use(UserSettingsContext); - if (source === null) { - return null; - } - if (isConnectedOptimization(source) && !enableInBrowserOptimization) { - return null; - } - return source; -}; diff --git a/libs/@hashintel/petrinaut/src/react/state/editor-context.ts b/libs/@hashintel/petrinaut/src/react/state/editor-context.ts index e2348528477..d36ffb178b4 100644 --- a/libs/@hashintel/petrinaut/src/react/state/editor-context.ts +++ b/libs/@hashintel/petrinaut/src/react/state/editor-context.ts @@ -14,7 +14,8 @@ export type DraggingStateByNodeId = Record< { dragging: boolean; position: { x: number; y: number } } >; -export type EditorGlobalMode = "edit" | "simulate" | "actual" | "notebook"; +export type EditorGlobalMode = "edit" | "simulate" | "actual"; +export type EditViewMode = "canvas" | "definitions"; type EditorEditionMode = | "cursor" | "add-place" @@ -44,6 +45,7 @@ export type SimulateDrawerState = export type EditorNavigationTarget = { globalMode?: EditorGlobalMode; + editViewMode?: EditViewMode; simulateViewMode?: SimulateViewMode; simulateDrawer?: SimulateDrawerState; selection?: SelectionMap; @@ -70,6 +72,7 @@ export type TimelineView = */ export type EditorState = { globalMode: EditorGlobalMode; + editViewMode: EditViewMode; editionMode: EditorEditionMode; cursorMode: CursorMode; isLeftSidebarOpen: boolean; @@ -138,6 +141,7 @@ export type EditorActions = { /** Navigate several editor surfaces as one app-history transition. */ navigateTo: (target: EditorNavigationTarget) => void; setGlobalMode: (mode: EditorGlobalMode) => void; + setEditViewMode: (view: EditViewMode) => void; setEditionMode: (mode: EditorEditionMode) => void; setCursorMode: (mode: CursorMode) => void; setLeftSidebarOpen: (isOpen: boolean) => void; @@ -194,6 +198,7 @@ export type EditorContextValue = EditorState & export const initialEditorState: EditorState = { globalMode: "edit", + editViewMode: "canvas", editionMode: "cursor", cursorMode: "pan", isLeftSidebarOpen: true, @@ -227,6 +232,7 @@ const DEFAULT_CONTEXT_VALUE: EditorContextValue = { ...initialEditorState, navigateTo: () => {}, setGlobalMode: () => {}, + setEditViewMode: () => {}, setEditionMode: () => {}, setCursorMode: () => {}, setLeftSidebarOpen: () => {}, diff --git a/libs/@hashintel/petrinaut/src/react/state/editor-provider.test.tsx b/libs/@hashintel/petrinaut/src/react/state/editor-provider.test.tsx index 6d99845f420..6ac24fa76a2 100644 --- a/libs/@hashintel/petrinaut/src/react/state/editor-provider.test.tsx +++ b/libs/@hashintel/petrinaut/src/react/state/editor-provider.test.tsx @@ -188,6 +188,23 @@ describe("EditorProvider selection gestures", () => { ); }); + it("records Edit view switches without changing the mode or selection", () => { + act(() => editor.setSelection(selectionOf("place-a"))); + recorded.length = 0; + act(() => editor.setEditViewMode("definitions")); + expect(editor.globalMode).toBe("edit"); + expect(editor.editViewMode).toBe("definitions"); + expect(editor.selection).toEqual(selectionOf("place-a")); + expect(recorded).toEqual([ + { history: "push", intent: { cause: "user", action: "edit-view" } }, + ]); + act(() => editor.setGlobalMode("simulate")); + act(() => editor.setGlobalMode("edit")); + expect(editor.editViewMode).toBe("definitions"); + act(() => editor.setEditViewMode("canvas")); + expect(editor.selection).toEqual(selectionOf("place-a")); + }); + const flushMicrotasks = () => act(async () => {}); it("coalesces react-flow batched updates and records one entry per gesture", async () => { diff --git a/libs/@hashintel/petrinaut/src/react/state/editor-provider.tsx b/libs/@hashintel/petrinaut/src/react/state/editor-provider.tsx index 17a15aeeb52..3df288b8823 100644 --- a/libs/@hashintel/petrinaut/src/react/state/editor-provider.tsx +++ b/libs/@hashintel/petrinaut/src/react/state/editor-provider.tsx @@ -294,6 +294,9 @@ export const EditorProvider: React.FC = ({ children }) => { (current) => ({ ...current, ...(target.globalMode !== undefined ? { mode: target.globalMode } : {}), + ...(target.editViewMode !== undefined + ? { editView: target.editViewMode } + : {}), ...(target.simulateViewMode !== undefined ? { simulateView: target.simulateViewMode, @@ -342,7 +345,9 @@ export const EditorProvider: React.FC = ({ children }) => { : "simulation-resource" : target.simulateViewMode !== undefined ? "simulation-view" - : "mode", + : target.editViewMode !== undefined + ? "edit-view" + : "mode", }, ); @@ -360,6 +365,7 @@ export const EditorProvider: React.FC = ({ children }) => { const actions: Omit = { navigateTo, setGlobalMode: (mode) => navigateTo({ globalMode: mode }), + setEditViewMode: (view) => navigateTo({ editViewMode: view }), setEditionMode: (mode) => setState((prev) => ({ ...prev, @@ -504,10 +510,18 @@ export const EditorProvider: React.FC = ({ children }) => { setHiddenTimelineSeriesIds: (seriesIds) => setState((prev) => ({ ...prev, hiddenTimelineSeriesIds: seriesIds })), setSimulateViewMode: (mode) => - navigateTo({ - simulateViewMode: mode, - simulateDrawer: { type: "closed" }, - }), + navigation.navigate( + (current) => + current.simulateView === mode + ? current + : { + ...current, + simulateView: mode, + simulateResource: null, + overlay: null, + }, + { cause: "user", action: "simulation-view" }, + ), setSimulateDrawer: (drawer) => navigateTo({ simulateDrawer: drawer }), setSearchOpen: (isOpen) => { scheduleAnimationEnd(); @@ -562,6 +576,7 @@ export const EditorProvider: React.FC = ({ children }) => { const effectiveState: EditorState = { ...state, globalMode: navigation.state.mode, + editViewMode: navigation.state.editView, simulateViewMode: navigation.state.simulateView, simulateDrawer: navigationResourceToSimulateDrawer( navigation.state.simulateResource, diff --git a/libs/@hashintel/petrinaut/src/react/state/use-effective-global-mode.ts b/libs/@hashintel/petrinaut/src/react/state/use-effective-global-mode.ts deleted file mode 100644 index 5c21f1bc014..00000000000 --- a/libs/@hashintel/petrinaut/src/react/state/use-effective-global-mode.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { use } from "react"; - -import { EditorContext } from "./editor-context"; -import { UserSettingsContext } from "./user-settings-context"; - -import type { EditorGlobalMode } from "./editor-context"; - -/** - * The global mode the editor actually renders. The stored mode can say - * "notebook" while the experimental notebook flag is off (e.g. the flag was - * disabled while the view was active); every consumer must agree that this - * falls back to "edit" — deriving it in one consumer only would render the - * edit canvas while mutations are still refused with a notebook explanation. - */ -export const useEffectiveGlobalMode = (): EditorGlobalMode => { - const { globalMode } = use(EditorContext); - const { enableNotebookView } = use(UserSettingsContext); - - return globalMode === "notebook" && !enableNotebookView ? "edit" : globalMode; -}; diff --git a/libs/@hashintel/petrinaut/src/react/state/use-read-only-reason.ts b/libs/@hashintel/petrinaut/src/react/state/use-read-only-reason.ts index c9caf5f6344..f944517d199 100644 --- a/libs/@hashintel/petrinaut/src/react/state/use-read-only-reason.ts +++ b/libs/@hashintel/petrinaut/src/react/state/use-read-only-reason.ts @@ -1,8 +1,8 @@ import { use } from "react"; import { SimulationContext } from "../simulation/context"; +import { EditorContext } from "./editor-context"; import { SDCPNContext } from "./sdcpn-context"; -import { useEffectiveGlobalMode } from "./use-effective-global-mode"; /** * Why the editor currently disallows mutations, or `null` when mutations @@ -26,10 +26,7 @@ export type ReadOnlyReason = */ export const useReadOnlyReason = (): ReadOnlyReason | null => { const { readonly } = use(SDCPNContext); - // The effective mode, not the stored one — the stored mode can say - // "notebook" while the flag is off, in which case the edit canvas renders - // and mutations must be allowed. - const globalMode = useEffectiveGlobalMode(); + const { globalMode } = use(EditorContext); const { state: simulationState } = use(SimulationContext); if (readonly) { diff --git a/libs/@hashintel/petrinaut/src/react/state/user-settings-context.ts b/libs/@hashintel/petrinaut/src/react/state/user-settings-context.ts index 3e5528a2467..62f7cf8a950 100644 --- a/libs/@hashintel/petrinaut/src/react/state/user-settings-context.ts +++ b/libs/@hashintel/petrinaut/src/react/state/user-settings-context.ts @@ -54,7 +54,6 @@ export type UserSettings = { highlightOnHover: boolean; partialSelection: boolean; enableNetComponents: boolean; - enableNotebookView: boolean; /** * Persisted preference controlling whether the product walkthrough opens * automatically the next time the app initializes. The live open state is @@ -62,18 +61,6 @@ export type UserSettings = { * only takes effect at the next init — not the current session. */ showWalkthroughOnInit: boolean; - /** - * Whether the WebGPU backend is offered at all. - * - * A master switch, not a choice of engine: with it on, each experiment picks - * its own backend as it is created, so a GPU and a CPU experiment can run side - * by side. Off means the per-experiment control is not shown. - * - * The backend is a restricted subset engine — bounded state, 32-bit numbers — - * and uses a different random generator, so it does not reproduce CPU - * trajectories seed for seed (it agrees statistically). - */ - webGpuEnabled: boolean; /** * Shows the Compilation tab in the bottom panel, which reports how the net's * user code lowered to HIR and what the GPU backend can take. @@ -82,23 +69,6 @@ export type UserSettings = { * only useful when you are debugging why something did not compile. */ showCompilationOutput: boolean; - /** - * Experimental: offer parameter sweeps. On, every numeric value of the - * experiment form gets an interval toggle — reading Sweep, or Optimize when - * In-browser optimization is on — that turns its value into an interval. - * Off, experiments take fixed values only. - */ - enableParameterSweeps: boolean; - /** - * Experimental: connect a host-supplied in-browser optimizer. On, the - * experiment form's interval toggles read Optimize: creating the experiment - * starts a study over the selected intervals, with an Objective and - * Constraints chosen in the form. Off, a connected optimizer counts as none - * at all, the toggles read Sweep and the sweep waits for a selection; any - * running in-browser optimization is cancelled. A remote optimization - * capability is unaffected either way. - */ - enableInBrowserOptimization: boolean; /** * Shows a host's Brunch demo affordances, such as the demo site's * prepared-fixture selector. Toggled from a palette command the host @@ -130,12 +100,8 @@ export type UserSettingsActions = { setHighlightOnHover: (value: boolean) => void; setPartialSelection: (value: boolean) => void; setEnableNetComponents: (value: boolean) => void; - setEnableNotebookView: (value: boolean) => void; setShowWalkthroughOnInit: (value: boolean) => void; - setWebGpuEnabled: (value: boolean) => void; setShowCompilationOutput: (value: boolean) => void; - setEnableParameterSweeps: (value: boolean) => void; - setEnableInBrowserOptimization: (value: boolean) => void; setBrunchDemoMode: (value: boolean) => void; updateSubViewSection: ( containerName: string, @@ -167,12 +133,8 @@ export const defaultUserSettings: UserSettings = { highlightOnHover: true, partialSelection: true, enableNetComponents: false, - enableNotebookView: false, showWalkthroughOnInit: true, - webGpuEnabled: false, showCompilationOutput: false, - enableParameterSweeps: false, - enableInBrowserOptimization: false, brunchDemoMode: false, subViewPanels: {}, canvasViewports: {}, @@ -203,12 +165,8 @@ export const defaultUserSettingsContextValue: UserSettingsContextValue = { setHighlightOnHover: () => {}, setPartialSelection: () => {}, setEnableNetComponents: () => {}, - setEnableNotebookView: () => {}, setShowWalkthroughOnInit: () => {}, - setWebGpuEnabled: () => {}, setShowCompilationOutput: () => {}, - setEnableParameterSweeps: () => {}, - setEnableInBrowserOptimization: () => {}, setBrunchDemoMode: () => {}, updateSubViewSection: () => {}, setCanvasViewport: () => {}, diff --git a/libs/@hashintel/petrinaut/src/react/state/user-settings-provider.test.tsx b/libs/@hashintel/petrinaut/src/react/state/user-settings-provider.test.tsx index 0f6ae45762d..144d1c16e2b 100644 --- a/libs/@hashintel/petrinaut/src/react/state/user-settings-provider.test.tsx +++ b/libs/@hashintel/petrinaut/src/react/state/user-settings-provider.test.tsx @@ -114,44 +114,63 @@ describe("UserSettingsProvider", () => { expect(screen.getByRole("button", { name: "probe: on" })).toBe(probe); }); - it("loads a blob written with the retired Ad-hoc scenarios key and drops the key on the next write", () => { - // An in-memory store: some Node versions expose a global `localStorage` - // whose methods are missing, so the test owns the storage it inspects. - const entries = new Map([ - [ - storageKey, - JSON.stringify({ - enableAdHocScenarios: true, - showWalkthroughOnInit: false, - }), - ], - ]); - vi.stubGlobal("localStorage", { - getItem: (key: string) => entries.get(key) ?? null, - setItem: (key: string, value: string) => entries.set(key, value), - removeItem: (key: string) => entries.delete(key), - }); - - try { - render( - - - , - ); - - fireEvent.click(screen.getByRole("button", { name: "walkthrough: off" })); - - const persisted = JSON.parse(entries.get(storageKey) ?? "{}") as Record< - string, - unknown - >; - expect("enableAdHocScenarios" in persisted).toBe(false); - expect(persisted.showWalkthroughOnInit).toBe(false); - expect(persisted.brunchDemoMode).toBe(true); - } finally { - vi.unstubAllGlobals(); - } - }); + it.each([false, true])( + "drops retired simulation flags from saved settings (legacy value=%s)", + (enabled) => { + // An in-memory store: some Node versions expose a global `localStorage` + // whose methods are missing, so the test owns the storage it inspects. + const entries = new Map([ + [ + storageKey, + JSON.stringify({ + enableAdHocScenarios: true, + enableNotebookView: enabled, + webGpuEnabled: enabled, + enableParameterSweeps: enabled, + enableInBrowserOptimization: enabled, + computeBackend: "webgpu", + showWalkthroughOnInit: false, + }), + ], + ]); + vi.stubGlobal("localStorage", { + getItem: (key: string) => entries.get(key) ?? null, + setItem: (key: string, value: string) => entries.set(key, value), + removeItem: (key: string) => entries.delete(key), + }); + + try { + render( + + + , + ); + + fireEvent.click( + screen.getByRole("button", { name: "walkthrough: off" }), + ); + + const persisted = JSON.parse(entries.get(storageKey) ?? "{}") as Record< + string, + unknown + >; + for (const key of [ + "enableAdHocScenarios", + "enableNotebookView", + "webGpuEnabled", + "enableParameterSweeps", + "enableInBrowserOptimization", + "computeBackend", + ]) { + expect(key in persisted).toBe(false); + } + expect(persisted.showWalkthroughOnInit).toBe(false); + expect(persisted.brunchDemoMode).toBe(true); + } finally { + vi.unstubAllGlobals(); + } + }, + ); it("reuses an ancestor provider, so a host and the editor share one state", () => { // The host mounts the provider above the editor and reads the settings diff --git a/libs/@hashintel/petrinaut/src/react/state/user-settings-provider.tsx b/libs/@hashintel/petrinaut/src/react/state/user-settings-provider.tsx index 7a9eb8bbc59..9664a6ecaef 100644 --- a/libs/@hashintel/petrinaut/src/react/state/user-settings-provider.tsx +++ b/libs/@hashintel/petrinaut/src/react/state/user-settings-provider.tsx @@ -23,11 +23,10 @@ const STORAGE_KEY = "petrinaut:user-settings"; /** The persisted blob, including keys no longer part of `UserSettings`. */ type PersistedUserSettings = Partial & { - /** - * Replaced by `webGpuEnabled` when the backend became a per-experiment choice. - * Still present in blobs written before that. - */ computeBackend?: "cpu" | "webgpu"; + webGpuEnabled?: boolean; + enableParameterSweeps?: boolean; + enableInBrowserOptimization?: boolean; /** * Chose between the entities tree and a stack of lists in the left panel. * The tree is the only rendering, so the key is dropped on the next write. @@ -43,6 +42,7 @@ type PersistedUserSettings = Partial & { * scenario form, so the key is dropped on the next write. */ enableAdHocScenarios?: boolean; + enableNotebookView?: boolean; }; const loadSettings = (): UserSettings => { @@ -52,17 +52,19 @@ const loadSettings = (): UserSettings => { // Destructured rather than read through the spread, so the dead key is // dropped from storage on the next write instead of persisting forever. const { - computeBackend, + computeBackend: _computeBackend, + webGpuEnabled: _webGpuEnabled, + enableParameterSweeps: _enableParameterSweeps, + enableInBrowserOptimization: _enableInBrowserOptimization, useEntitiesTreeView: _useEntitiesTreeView, enableOptimizationSurface: _enableOptimizationSurface, enableAdHocScenarios: _enableAdHocScenarios, + enableNotebookView: _enableNotebookView, ...parsed } = JSON.parse(raw) as PersistedUserSettings; return { ...defaultUserSettings, ...parsed, - // Someone who had selected the GPU globally keeps it available. - webGpuEnabled: parsed.webGpuEnabled ?? computeBackend === "webgpu", }; } } catch { @@ -130,16 +132,10 @@ const OwnedUserSettingsProvider: React.FC = ({ setState((prev) => ({ ...prev, partialSelection: value })), setEnableNetComponents: (value: boolean) => setState((prev) => ({ ...prev, enableNetComponents: value })), - setEnableNotebookView: (value: boolean) => - setState((prev) => ({ ...prev, enableNotebookView: value })), setShowWalkthroughOnInit: (value: boolean) => setState((prev) => ({ ...prev, showWalkthroughOnInit: value })), - setWebGpuEnabled: (value: boolean) => - setState((prev) => ({ ...prev, webGpuEnabled: value })), setShowCompilationOutput: (value: boolean) => setState((prev) => ({ ...prev, showCompilationOutput: value })), - setEnableParameterSweeps: (value: boolean) => - setState((prev) => ({ ...prev, enableParameterSweeps: value })), setCanvasViewport: (petriNetId: string, viewport: CanvasViewport) => { // Stamped out here: an updater runs more than once and has to be pure. const savedAt = Date.now(); @@ -153,8 +149,6 @@ const OwnedUserSettingsProvider: React.FC = ({ ), })); }, - setEnableInBrowserOptimization: (value: boolean) => - setState((prev) => ({ ...prev, enableInBrowserOptimization: value })), setBrunchDemoMode: (value: boolean) => setState((prev) => ({ ...prev, brunchDemoMode: value })), updateSubViewSection: ( diff --git a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/ad-hoc-scenario-form.test.tsx b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/ad-hoc-scenario-form.test.tsx index 6f539ecc131..490bb5cba01 100644 --- a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/ad-hoc-scenario-form.test.tsx +++ b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/ad-hoc-scenario-form.test.tsx @@ -81,6 +81,7 @@ const context: AdHocSynthesisContext = { name: "Pumps", colorId: "colour-pump", dynamicsEnabled: false, + showAsInitialState: true, differentialEquationId: null, x: 0, y: 0, @@ -90,6 +91,7 @@ const context: AdHocSynthesisContext = { name: "Queue", colorId: null, dynamicsEnabled: false, + showAsInitialState: true, differentialEquationId: null, x: 0, y: 0, @@ -147,6 +149,35 @@ const colouredPlace = (state: AdHocScenarioState | undefined) => { }; describe("AdHocScenarioForm", () => { + it("filters starting places without changing their authored state", async () => { + const onChange = vi.fn(); + render( + ({ + ...place, + showAsInitialState: place.id === "place-queue", + })), + }} + selection="none" + />, + ); + expect(screen.queryByRole("button", { name: "Pumps place" })).toBeNull(); + expect(screen.getByRole("button", { name: "Queue › count" })).toBeTruthy(); + fireEvent.click(screen.getByRole("checkbox", { name: "Show all places" })); + expect( + await screen.findByRole("button", { name: "Pumps place" }), + ).toBeTruthy(); + fireEvent.click(screen.getByRole("checkbox", { name: "Show all places" })); + await waitFor(() => + expect(screen.queryByRole("button", { name: "Pumps place" })).toBeNull(), + ); + expect(onChange).not.toHaveBeenCalled(); + }); + it("selects a row's kind from the gutter menu", async () => { let latest: AdHocScenarioState | undefined; render( @@ -360,7 +391,7 @@ describe("AdHocScenarioForm", () => { expect(place.count.expression).toBe("parameters.rate * 4"); }); - it("renders a synthesis error on the closed slot's trigger", () => { + it("keeps bound errors visible when the editor opens", async () => { const initial: AdHocScenarioState = { variables: [], netParameters: [], @@ -389,7 +420,33 @@ describe("AdHocScenarioForm", () => { const trigger = screen.getByRole("button", { name: "Pumps › item 0 › pressure", }); - expect(trigger.getAttribute("title")).toContain("nope"); + const error = trigger.getAttribute("title"); + expect(error).toBeTruthy(); + fireEvent.click(trigger); + expect((await screen.findByRole("alert")).textContent).toBe(error); + }); + + it("shows an invalid ratio value below valid optimization bounds", async () => { + render( + , + ); + const trigger = screen.getByTitle(/between 0 and 1/); + fireEvent.click(trigger); + expect((await screen.findByRole("alert")).textContent).toContain( + "between 0 and 1", + ); }); it("removes the row when Delete is pressed on its gutter", async () => { @@ -627,11 +684,13 @@ describe("AdHocScenarioForm", () => { it("walks between the form's members and toggles sections from their headers", async () => { render(); - // Variables lead the form; down from the parameters grid (one row) - // lands on the Initial state section header. + // A model with only starting places needs no visibility toggle. const rateValue = screen.getByRole("button", { name: "Rate" }); rateValue.focus(); fireEvent.keyDown(rateValue, { key: "ArrowDown" }); + expect( + screen.queryByRole("checkbox", { name: "Show all places" }), + ).toBeNull(); expect(document.activeElement).toBe( screen.getByRole("button", { name: "Toggle Initial state section" }), ); @@ -1246,6 +1305,16 @@ describe("AdHocScenarioForm", () => { fireEvent.keyDown(addVariable, { key: "ArrowLeft" }); expect(document.activeElement).toBe(optimizeToggle); + pumpsHeader.focus(); + fireEvent.keyDown(pumpsHeader, { key: "ArrowLeft" }); + expect(pumpsHeader.getAttribute("aria-expanded")).toBe("false"); + expect(document.activeElement).toBe(pumpsHeader); + fireEvent.keyDown(pumpsHeader, { key: "ArrowLeft" }); + expect(document.activeElement).toBe(optimizeToggle); + fireEvent.keyDown(optimizeToggle, { key: "ArrowRight" }); + expect(document.activeElement).toBe(pumpsHeader); + fireEvent.keyDown(pumpsHeader, { key: "ArrowRight" }); + // Within the places column the walk still chains: up from the token // table's column header lands on the place's own add-variable line. const pressureHeader = screen.getByRole("button", { diff --git a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/ad-hoc-scenario-form.tsx b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/ad-hoc-scenario-form.tsx index 4625625f358..7fe3d78ca77 100644 --- a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/ad-hoc-scenario-form.tsx +++ b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/ad-hoc-scenario-form.tsx @@ -35,6 +35,7 @@ import { use, useEffect, useRef, useState } from "react"; +import { Toggle } from "@hashintel/ds-components"; import { css, cx } from "@hashintel/ds-helpers/css"; import { adHocPlaceStateFor, @@ -46,7 +47,8 @@ import { } from "@hashintel/petrinaut-core"; import { LanguageClientContext } from "../../../react/lsp/context"; -import { FocusRoot, FocusStack } from "../../worksheet/focus-stack"; +import { FocusControls } from "../../worksheet/focus-controls"; +import { FocusStack } from "../../worksheet/focus-stack"; import { useFocusClearance } from "../../worksheet/use-focus-clearance"; import { useFocusHeader } from "../../worksheet/use-focus-member"; import { Section, SectionList } from "../section"; @@ -59,6 +61,8 @@ import { useAdHocLspSession } from "./use-ad-hoc-lsp-session"; import { useAdHocFormHistory } from "./use-form-history"; import { VariableRows } from "./variable-rows"; +export { FormSectionHeader } from "./form-section-header"; + import type { AdHocFocusTarget } from "./dependency-highlight"; import type { AdHocFormMode, @@ -69,6 +73,7 @@ import type { AdHocScenarioState, AdHocSlot, AdHocSynthesisContext, + AdHocValueTarget, } from "@hashintel/petrinaut-core"; // The CSS twin of useFocusClearance (which carries the shared 25px @@ -84,7 +89,22 @@ const focusClearanceStyle = css({ const placesListStyle = css({ display: "flex", flexDirection: "column", + gap: "1", +}); + +const placesVisibilityStyle = css({ + marginLeft: "1", gap: "1.5", + paddingY: "1", + flexShrink: "0", + whiteSpace: "nowrap", + "& [data-part='label']": { fontSize: "[11px]" }, + "& [data-part='control']": { + "--toggle-width": "22px", + "--toggle-height": "12px", + "--toggle-padding": "1px", + "--toggle-thumb-size": "8px", + }, }); export interface AdHocScenarioFormProps { @@ -100,6 +120,7 @@ export interface AdHocScenarioFormProps { * everything else is read-only yet keyboard-navigable and selectable. */ mode?: AdHocFormMode; + expressionFor?: (target: AdHocValueTarget) => string | undefined; /** * Custom arrangement: the host receives each group — already wired to the * form's contexts — and lays them out itself (e.g. Simulation Settings @@ -115,6 +136,7 @@ export interface AdHocScenarioFormProps { variables: React.ReactNode; parameters: React.ReactNode; places: React.ReactNode; + placesVisibilityControl: React.ReactNode; }) => React.ReactNode; /** Classname for the form's root element (the keyboard-handling div). */ className?: string; @@ -144,14 +166,16 @@ const NavigableSection: React.FC<{ title: string; tooltip: string; children: React.ReactNode; -}> = ({ title, tooltip, children }) => { + titleAction?: React.ReactNode; +}> = ({ title, tooltip, children, titleAction }) => { const [open, setOpen] = useState(true); const header = useFocusHeader({ - collapse: () => setOpen(false), - expand: () => setOpen(true), + collapse: open ? () => setOpen(false) : undefined, + expand: open ? undefined : () => setOpen(true), }); return (
{children}
@@ -172,11 +197,13 @@ export const AdHocScenarioForm: React.FC = ({ context, selection, mode = "author", + expressionFor = () => undefined, renderLayout, className, sessionId: externalSessionId, }) => { const sessionId = useAdHocLspSession(state, externalSessionId); + const [showAllPlaces, setShowAllPlaces] = useState(false); const { diagnosticsByUri, requestFormatExpression } = use( LanguageClientContext, ); @@ -300,6 +327,7 @@ export const AdHocScenarioForm: React.FC = ({ setFocusedValue, formatExpression: requestFormatExpression, mode, + expressionFor, dense: renderLayout !== undefined, overlayKeyDown: { capture: handleKeyDown, bubble: stopDeleteKeys }, }; @@ -322,9 +350,44 @@ export const AdHocScenarioForm: React.FC = ({ /> ); + const visiblePlaces = context.places.filter( + (place) => showAllPlaces || place.showAsInitialState, + ); + const hasOtherPlaces = context.places.some( + (place) => !place.showAsInitialState, + ); + const placesVisibilityControl = hasOtherPlaces ? ( + + + + ) : null; const placesList = ( -
- {context.places.map((place) => { +
+ {visiblePlaces.length === 0 ? ( +

+ {hasOtherPlaces + ? "No default starting places. Turn on “Show all places” to inspect the initial state." + : "No places defined."} +

+ ) : null} + {visiblePlaces.map((place) => { const placeState = adHocPlaceStateFor(state, context, place.id); const colour = place.colorId ? context.types.find((type) => type.id === place.colorId) @@ -356,57 +419,57 @@ export const AdHocScenarioForm: React.FC = ({ return ( - - {/* Undo/redo listens in the capture phase, so it sees keys before any + {/* Undo/redo listens in the capture phase, so it sees keys before any cell handler; open text fields and Monaco pass through untouched. */} -
- {renderLayout ? ( - - {renderLayout({ - variables: variableRows, - parameters: parameterRows, - places: placesList, - })} - - ) : ( - - - - {variableRows} - - - {parameterRows ? ( - - {parameterRows} - - ) : null} +
+ {renderLayout ? ( + + {renderLayout({ + variables: variableRows, + parameters: parameterRows, + places: placesList, + placesVisibilityControl, + })} + + ) : ( + + + + {variableRows} + + {parameterRows ? ( - {placesList} + {parameterRows} - - - )} -
- + ) : null} + + + {placesList} + +
+
+ )} +
); }; diff --git a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/form-context.ts b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/form-context.ts index 63f0c046e85..fe296429675 100644 --- a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/form-context.ts +++ b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/form-context.ts @@ -79,6 +79,7 @@ export type AdHocFormMode = "author" | "run"; export interface AdHocFormServices { /** What the form lets the user change; see {@link AdHocFormMode}. */ mode: AdHocFormMode; + expressionFor: (target: AdHocValueTarget) => string | undefined; /** The whole form state, as currently edited. */ formState: AdHocScenarioState; /** @@ -139,6 +140,7 @@ export interface AdHocFormServices { export const AdHocFormContext = createContext({ mode: "author", + expressionFor: () => undefined, formState: { variables: [], netParameters: [], places: {} }, dispatch: () => {}, synthesisContext: { netParameters: [], places: [], types: [] }, diff --git a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/form-section-header.tsx b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/form-section-header.tsx new file mode 100644 index 00000000000..06f9efadaeb --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/form-section-header.tsx @@ -0,0 +1,53 @@ +import { css } from "@hashintel/ds-helpers/css"; + +import { FocusStack } from "../../worksheet/focus-stack"; +import { PointerHelpTooltip } from "../pointer-help-tooltip"; +import { StackedSectionHeader } from "../stacked-sections"; + +const headerStyle = css({ + position: "sticky", + top: "[0]", + zIndex: "[2]", + display: "flex", + alignItems: "center", + gap: "1", + minHeight: "[28px]", + paddingY: "1", + backgroundColor: "neutral.s00", +}); +const titleStyle = css({ + fontSize: "[var(--form-heading-size, 12px)]", + fontWeight: "semibold", + color: "neutral.a100", + textTransform: "[var(--form-heading-case, none)]", + letterSpacing: "[var(--form-heading-spacing, normal)]", +}); +const actionsStyle = css({ + display: "flex", + alignItems: "center", + gap: "1", + marginLeft: "auto", +}); + +export const FormSectionHeader: React.FC<{ + title: string; + titleAction?: React.ReactNode; + tooltip?: string; + spaceBefore?: boolean; + children?: React.ReactNode; +}> = ({ title, titleAction, tooltip, spaceBefore, children }) => ( + + {(renderTitle) => ( + <> + {renderTitle(title, titleStyle)} + {tooltip ? : null} + {titleAction || children ? ( + + {titleAction} + {children ?
{children}
: null} +
+ ) : null} + + )} +
+); diff --git a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/parameter-rows.tsx b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/parameter-rows.tsx index 388245a8923..502ab74046e 100644 --- a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/parameter-rows.tsx +++ b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/parameter-rows.tsx @@ -101,7 +101,7 @@ export const ParameterRows: React.FC = ({ entries }) => { target={target} value={entry} display={ - entry.optimize + entry.optimize || mode === "run" ? undefined : entry.expression || ( diff --git a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/place-block.tsx b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/place-block.tsx index cec79ba52c1..fb64b394478 100644 --- a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/place-block.tsx +++ b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/place-block.tsx @@ -6,9 +6,6 @@ * grid-track transition and makes the content inert; collapsed, the place * is one line: its name and a summary of its rows and token total. An * uncoloured place is a header plus one full-width count cell. - * - * The collapse chevron hangs in the left margin, so every place name — - * coloured or not — starts at the same x as the blocks beneath it. */ import { use, useState } from "react"; @@ -31,20 +28,16 @@ import type { } from "@hashintel/petrinaut-core"; const blockStyle = css({ - contentVisibility: "auto", - containIntrinsicSize: "[auto 200px]", display: "flex", flexDirection: "column", gap: "1.5", + "&[data-collapsed]": { gap: "0" }, }); const denseBlockStyle = css({ gap: "1", }); -// One line grammar for places: the fixed title width and line height are -// shared between an uncoloured place's row and a collapsed coloured -// place's header, so counts and collapsed summaries start at the same x. const headerStyle = css({ display: "flex", alignItems: "center", @@ -52,9 +45,6 @@ const headerStyle = css({ minHeight: "[26px]", }); -// The place-name trigger pulls itself left by its padding plus the chevron -// slot, so the dot + name align with the un-chevroned headers and the -// tables below. const placeNameButtonStyle = css({ display: "flex", alignItems: "center", @@ -62,10 +52,11 @@ const placeNameButtonStyle = css({ border: "none", background: "[transparent]", padding: "[2px 4px]", - marginLeft: "[-20px]", + marginLeft: "[-24px]", + minWidth: "[0]", borderRadius: "xs", - fontSize: "sm", - fontWeight: "semibold", + fontSize: "xs", + fontWeight: "medium", color: "neutral.s120", cursor: "pointer", _hover: { backgroundColor: "neutral.s10" }, @@ -76,29 +67,22 @@ const placeNameButtonStyle = css({ }, }); -// Collapsed, the title button takes the shared fixed width (the 20px -// chevron hang included), so the summary aligns with the uncoloured -// places' count cells. -const collapsedTitleButtonStyle = css({ - width: "[190px]", -}); - -const collapsedTitleNameStyle = css({ +const placeTitleNameStyle = css({ flex: "1", minWidth: "[0]", overflow: "hidden", whiteSpace: "nowrap", textAlign: "left", - maskImage: - "[linear-gradient(to right, black calc(100% - 14px), transparent)]", + textOverflow: "ellipsis", }); const chevronStyle = css({ display: "flex", alignItems: "center", justifyContent: "center", - color: "neutral.s70", + color: "neutral.s100", width: "[12px]", + flexShrink: "0", transition: "[transform 0.12s ease]", }); @@ -115,6 +99,8 @@ const colourDotStyle = css({ }); const summaryStyle = css({ + marginLeft: "auto", + flexShrink: "0", fontFamily: "mono", fontSize: "[10px]", color: "neutral.s80", @@ -153,20 +139,9 @@ const placeNameStyle = css({ alignItems: "center", gap: "1", padding: "[2px 0]", - fontSize: "sm", - fontWeight: "semibold", - color: "neutral.s120", -}); - -// The embedded (dense) rendering shrinks the titles a step and tightens -// their padding, so a long place list stays scannable in a panel. -const densePlaceNameStyle = css({ fontSize: "xs", fontWeight: "medium", -}); - -const headerSpacerStyle = css({ - flex: "1", + color: "neutral.s120", }); // An uncoloured place is one line: a fixed-width title, then the count @@ -187,7 +162,8 @@ const uncolouredCountTriggerStyle = css({ }); const uncolouredTitleStyle = css({ - width: "[170px]", + marginLeft: "[-4px]", + width: "[194px]", flexShrink: "0", overflow: "hidden", whiteSpace: "nowrap", @@ -250,22 +226,22 @@ export const ColouredPlaceBlock: React.FC = ({ setEverExpanded(true); } const { attach: attachHeader, onHeaderKeyDown } = useFocusHeader({ - collapse: () => setCollapsed(true), - expand: () => setCollapsed(false), + collapse: collapsed ? undefined : () => setCollapsed(true), + expand: collapsed ? () => setCollapsed(false) : undefined, }); const total = placeTotal(place.id); const totalText = total.resolved ? `${total.total}` : total.text; return ( -
+
- {collapsed ? ( - 0 || (total.resolved && total.total > 0)) && - summaryFilledStyle, - )} - > - {state.rows.length} row{state.rows.length === 1 ? "" : "s"} ·{" "} - {totalText} tokens - - ) : null} - + 0 || (total.resolved && total.total > 0)) && + summaryFilledStyle, + )} + > + {collapsed + ? `${state.rows.length} row${state.rows.length === 1 ? "" : "s"} · ` + : null} + {totalText} tokens +
= ({ place, state, }) => { - const { mode, dense } = use(AdHocFormContext); + const { mode } = use(AdHocFormContext); const target = { kind: "count" as const, placeId: place.id, row: null }; // The count cell is a single-element member: vertical arrows leave to the // neighbouring member, horizontal ones cross into a sibling column. @@ -336,19 +304,13 @@ export const UncolouredPlaceBlock: React.FC = ({ return (
- +
` — the Select drops `className` — and - * the visible box is an unnamed div under `[data-part='root']` styled by - * base-input variables, so the neutralization hits every div beneath the - * root; the dropdown itself is portalled out and keeps its own look. + * Fits the Select and its chevron inside the spreadsheet cell. The visible + * box is the root's direct div, whose ::after draws the chevron; the + * dropdown is portalled out and keeps its own styles. */ export const cellSelectStyle = css({ "& [data-part='root']": { width: "[100%!]", - minWidth: "[100%!]", + minWidth: "[0!]", height: "[28px!]", minHeight: "[0!]", + "--form-min-width": "0px", "--base-input-border-radius": "0px", "--base-input-background-color": "transparent", _hover: { @@ -398,21 +399,21 @@ export const cellSelectStyle = css({ boxShadow: "[none!]", backgroundColor: "[transparent!]", }, - // The trigger fills its cell so every select is the same width and the - // chevron sits at the same x; plain (non-code) face, quiet indicator. + "& [data-part='root'] > div": { + width: "[100%!]", + _after: { + width: "[5px]", + height: "[5px]", + marginLeft: "[6px]", + marginRight: "[8px]", + color: "neutral.s60", + }, + }, "& [data-part='trigger']": { width: "[100%!]", - justifyContent: "space-between", fontSize: "xs", height: "[28px!]", minHeight: "[0!]", paddingY: "[0!]", }, - "& [data-part='indicator']": { - color: "neutral.s60", - }, - "& [data-part='indicator'] svg": { - width: "[11px]", - height: "[11px]", - }, }); diff --git a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/token-table.tsx b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/token-table.tsx index ea63013f8b1..b6324b9ecca 100644 --- a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/token-table.tsx +++ b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/token-table.tsx @@ -9,8 +9,7 @@ * puts one value slot directly below the header, and renders the cells * beneath as dimmed derived copies whose clicks edit the shared value. A * phantom trailing row follows the cell selection model — the first click - * selects, the second (or Enter, or the gutter's +) materializes a fixed - * row — and the place's token total sits at the bottom. + * selects, the second (or Enter, or the gutter's +) materializes a fixed row. * * The whole table is a keyboard grid. Arrows and Tab move between cells; * vertical moves pass through the shared-values line and each dynamic row's @@ -132,18 +131,6 @@ const stripEditorOptimizedStyle = css({ backgroundColor: "[transparent]", }); -// The place total sits under the spreadsheet, outside it — one tight line, -// so it doesn't widen the gap to the next place block. -const totalTextStyle = css({ - textAlign: "right", - fontFamily: "mono", - fontSize: "[10px]", - lineHeight: "[1.2]", - color: "neutral.s80", - paddingX: "1", - marginTop: "[1px]", -}); - const targetKey = ({ stopId, column }: FocusStopTarget): string => `${stopId}:${column}`; @@ -158,8 +145,7 @@ export const TokenTable: React.FC = ({ colour, state, }) => { - const { mode, placeTotal, selection, setFocusedValue, dispatch } = - use(AdHocFormContext); + const { mode, selection, setFocusedValue, dispatch } = use(AdHocFormContext); // Run mode: cells and gutters stay focusable and walkable, but nothing // edits and no rows are added or removed. const readOnly = mode === "run"; @@ -198,7 +184,6 @@ export const TokenTable: React.FC = ({ const sharedColumnIndexes = elements.flatMap((element, columnIndex) => state.sharedColumns[element.name] ? [columnIndex] : [], ); - const total = placeTotal(place.id); // Every keyboard-reachable line of the table, top to bottom, declared as // worksheet stops: the column headers, the shared values (a sparse line), @@ -524,179 +509,174 @@ export const TokenTable: React.FC = ({ }; return ( - <> - - - - - {elements.map((element, columnIndex) => { - const shared = Boolean(state.sharedColumns[element.name]); - return ( - + + + + {elements.map((element, columnIndex) => { + const shared = Boolean(state.sharedColumns[element.name]); + return ( + + - - - - - ); - })} - - - - {/* Shared values: one slot directly below each shared header. */} - {hasSharedColumns ? ( - - - - {elements.map((element, columnIndex) => { - const shared = state.sharedColumns[element.name]; - if (!shared) { - return ; - } - const target = { - kind: "column" as const, - placeId: place.id, - column: columnIndex, - }; - return ( - - - onFocusTarget({ - stopId: "shared", - column: columnIndex, - }) - } - onTriggerKeyDown={onKeyDown({ - stopId: "shared", - column: columnIndex, - })} - /> - - ); - })} - - - ) : null} - - {state.rows.map((row, rowIndex) => renderDataRow(row, rowIndex))} + {element.name} + + + + ); + })} + + - {/* Phantom trailing row: a first click selects a phantom cell; a - click on the selected cell, a double-click, or Enter materializes - the row. The gutter's + materializes directly. Run mode adds no - rows, so the line is gone entirely. */} - {readOnly ? null : ( - - materializeRow(0)} - > - {elements.map((element, columnIndex) => ( + {/* Shared values: one slot directly below each shared header. */} + {hasSharedColumns ? ( + + + + {elements.map((element, columnIndex) => { + const shared = state.sharedColumns[element.name]; + if (!shared) { + return ; + } + const target = { + kind: "column" as const, + placeId: place.id, + column: columnIndex, + }; + return ( - + ); return ( <> - + {trigger} + {showSource ? ( + + ) : null} {open && rect ? (
= ({ />
) : null} - {boundsError ? ( -
{boundsError}
+ {error ? ( +
+ {error} +
) : null}
diff --git a/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/value-editor/computed-expression.tsx b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/value-editor/computed-expression.tsx new file mode 100644 index 00000000000..fb9b75f383a --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/components/ad-hoc-scenario-form/value-editor/computed-expression.tsx @@ -0,0 +1,110 @@ +import { Portal } from "@ark-ui/react/portal"; +import { useLayoutEffect, useState } from "react"; + +import { usePortalContainerRef } from "@hashintel/ds-components"; +import { css } from "@hashintel/ds-helpers/css"; + +import { OverlayScrollArea } from "../../overlay-scroll-area"; + +const expressionStyle = css({ + position: "fixed", + zIndex: "popover", + display: "flex", + flexDirection: "column", + backgroundColor: "neutral.s00", + color: "neutral.fg.body", + border: "[1px solid {colors.blue.s70}]", + borderRadius: "xs", + boxShadow: "[0 3px 12px -4px rgba(0,0,0,0.18)]", + pointerEvents: "auto", +}); +const codeStyle = css({ + margin: "[0]", + paddingX: "2", + paddingY: "1", + fontFamily: "mono", + fontSize: "[12px]", + lineHeight: "[18px]", + whiteSpace: "pre-wrap", + overflowWrap: "anywhere", + userSelect: "text", +}); + +export const ComputedExpression: React.FC<{ + id: string; + expression: string; + anchorRef: React.RefObject; +}> = ({ id, expression, anchorRef }) => { + const portalContainerRef = usePortalContainerRef(); + const [bounds, setBounds] = useState<{ + top: number; + left: number; + width: number; + minHeight: number; + maxHeight: number; + } | null>(null); + const [visible, setVisible] = useState(true); + useLayoutEffect(() => { + const anchor = anchorRef.current; + if (!anchor) { + return; + } + const measure = () => { + const rect = anchor.getBoundingClientRect(); + const longestLine = Math.max( + ...expression.split("\n").map((line) => line.length), + ); + const width = Math.min( + window.innerWidth - 16, + Math.max(rect.width, Math.min(560, longestLine * 7.3 + 18)), + ); + setBounds({ + top: rect.top, + left: Math.max( + 8, + rect.left + width <= window.innerWidth - 8 + ? rect.left + : rect.right - width, + ), + width, + minHeight: rect.height, + maxHeight: Math.max( + rect.height, + Math.min(240, window.innerHeight - rect.top - 8), + ), + }); + }; + const resize = new ResizeObserver(measure); + resize.observe(anchor); + const intersection = new IntersectionObserver(([entry]) => + setVisible(entry?.isIntersecting ?? false), + ); + intersection.observe(anchor); + window.addEventListener("resize", measure); + window.addEventListener("scroll", measure, true); + measure(); + return () => { + resize.disconnect(); + intersection.disconnect(); + window.removeEventListener("resize", measure); + window.removeEventListener("scroll", measure, true); + }; + }, [anchorRef, expression]); + + return bounds && visible ? ( + +
event.preventDefault()} + style={bounds} + > + +
+            {expression}
+          
+
+
+
+ ) : null; +}; diff --git a/libs/@hashintel/petrinaut/src/ui/components/overlay-scroll-area.tsx b/libs/@hashintel/petrinaut/src/ui/components/overlay-scroll-area.tsx new file mode 100644 index 00000000000..82f78f61ec4 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/components/overlay-scroll-area.tsx @@ -0,0 +1,71 @@ +import { ScrollArea } from "@ark-ui/react/scroll-area"; + +import { css, cx } from "@hashintel/ds-helpers/css"; + +const rootStyle = css({ + display: "flex", + flexDirection: "column", + flex: "[1]", + minHeight: "[0]", + minWidth: "[0]", + overflow: "hidden", +}); +const viewportStyle = css({ + flex: "[1]", + minHeight: "[0]", + minWidth: "[0]", + scrollbarWidth: "[none]", + scrollbarGutter: "auto", + "&::-webkit-scrollbar": { display: "none" }, +}); +const scrollbarStyle = css({ + zIndex: "[4]", + padding: "[2px]", + opacity: "[0]", + pointerEvents: "none", + transition: "[opacity 120ms ease]", + "&[data-hover], &[data-dragging]": { opacity: "[1]", pointerEvents: "auto" }, + "&[data-orientation=vertical]": { width: "[8px]" }, + "&[data-orientation=horizontal]": { height: "[8px]" }, + "&[data-orientation=vertical]:not([data-overflow-y]), &[data-orientation=horizontal]:not([data-overflow-x])": + { display: "none" }, + "@media (prefers-reduced-motion: reduce)": { transition: "[none]" }, +}); +const thumbStyle = css({ + borderRadius: "full", + backgroundColor: "neutral.a60", + "&[data-orientation=horizontal]": { height: "[100%]" }, + "&[data-orientation=vertical]": { width: "[100%]" }, +}); + +export const overlayScrollDrawerBodyStyle = css({ + display: "flex", + overflow: "hidden", +}); +export const overlayScrollDrawerViewportStyle = css({ + paddingX: "[var(--panel-horizontal-padding)]", + paddingBottom: "5", +}); + +export const OverlayScrollArea: React.FC<{ + children: React.ReactNode; + className?: string; + viewportClassName?: string; +}> = ({ children, className, viewportClassName }) => ( + + + + {children} + + + + + + + + + +); diff --git a/libs/@hashintel/petrinaut/src/ui/components/pointer-help-tooltip.test.tsx b/libs/@hashintel/petrinaut/src/ui/components/pointer-help-tooltip.test.tsx new file mode 100644 index 00000000000..87f6b249f51 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/components/pointer-help-tooltip.test.tsx @@ -0,0 +1,31 @@ +/** @vitest-environment jsdom */ +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, expect, it } from "vitest"; + +import { FocusControls } from "../worksheet/focus-controls"; +import { PointerHelpTooltip } from "./pointer-help-tooltip"; + +afterEach(cleanup); + +it("keeps informational tooltips out of Tab and arrow navigation after rendering", () => { + const toolbar = (content: string) => ( + + + + + + ); + const { container, rerender } = render(toolbar("Help")); + const trigger = container.querySelector( + '[data-scope="tooltip"][data-part="trigger"]', + ); + expect(trigger?.tabIndex).toBe(-1); + rerender(toolbar("Updated help")); + expect(trigger?.tabIndex).toBe(-1); + const scenario = screen.getByRole("button", { name: "Scenario" }); + scenario.focus(); + fireEvent.keyDown(scenario, { key: "ArrowRight" }); + expect(document.activeElement).toBe( + screen.getByRole("spinbutton", { name: "Time step" }), + ); +}); diff --git a/libs/@hashintel/petrinaut/src/ui/components/pointer-help-tooltip.tsx b/libs/@hashintel/petrinaut/src/ui/components/pointer-help-tooltip.tsx new file mode 100644 index 00000000000..0726a975a45 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/components/pointer-help-tooltip.tsx @@ -0,0 +1,31 @@ +import { useEffect, useRef } from "react"; + +import { HelpTooltip } from "@hashintel/ds-components"; +import { css, cx } from "@hashintel/ds-helpers/css"; + +const wrapperStyle = css({ + display: "inline-flex", + alignItems: "center", + flexShrink: "0", +}); +const iconStyle = css({ top: "[0]", marginLeft: "[0]", display: "block" }); + +export const PointerHelpTooltip: React.FC< + React.ComponentProps +> = ({ className, ...props }) => { + const rootRef = useRef(null); + useEffect(() => { + // DS adds a tab stop to informational icons; these forms reserve stops for controls. + const trigger = rootRef.current?.querySelector( + '[data-scope="tooltip"][data-part="trigger"]', + ); + if (trigger) { + trigger.tabIndex = -1; + } + }); + return ( + + + + ); +}; diff --git a/libs/@hashintel/petrinaut/src/ui/components/scroll-fade.tsx b/libs/@hashintel/petrinaut/src/ui/components/scroll-fade.tsx new file mode 100644 index 00000000000..b49166e4546 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/components/scroll-fade.tsx @@ -0,0 +1,69 @@ +import { css } from "@hashintel/ds-helpers/css"; + +import type { CSSProperties } from "react"; + +const fadeStyle = css({ + position: "absolute", + left: "[0]", + right: "[0]", + pointerEvents: "none", + zIndex: "[3]", + "&[data-animated]": { + transition: "[opacity 150ms ease]", + "@media (prefers-reduced-motion: reduce)": { transition: "[none]" }, + }, +}); + +const layerStyle = css({ + position: "absolute", + inset: "[0]", +}); + +export const ScrollFade: React.FC<{ + edge: "top" | "bottom"; + visible: boolean; + size?: number; + blur?: number; + offset?: CSSProperties["top"]; + color?: string; + animated?: boolean; +}> = ({ + edge, + visible, + size = 16, + blur = 0, + offset = 0, + color = "var(--colors-neutral-s00)", + animated = true, +}) => ( + +); diff --git a/libs/@hashintel/petrinaut/src/ui/components/section.test.tsx b/libs/@hashintel/petrinaut/src/ui/components/section.test.tsx index b996e2c862f..d8822775e5a 100644 --- a/libs/@hashintel/petrinaut/src/ui/components/section.test.tsx +++ b/libs/@hashintel/petrinaut/src/ui/components/section.test.tsx @@ -1,11 +1,13 @@ /** * @vitest-environment jsdom - * - * Stacking is the one thing about a Section that jsdom can still hold to - * account: it computes no layout, but the classes Panda emits carry the - * tiers, and the tiers are what the nesting bug was about. */ -import { cleanup, render, screen } from "@testing-library/react"; +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; import { afterEach, describe, expect, it } from "vitest"; import { Section, SectionList } from "./section"; @@ -13,6 +15,22 @@ import { Section, SectionList } from "./section"; afterEach(cleanup); describe("Section", () => { + it("exposes the expanded state on its keyboard-accessible toggle", async () => { + render( +
+ Condition +
, + ); + const trigger = screen.getByRole("button", { + name: "Toggle Constraints section", + }); + expect(trigger.getAttribute("aria-expanded")).toBe("true"); + fireEvent.click(trigger); + await waitFor(() => + expect(trigger.getAttribute("aria-expanded")).toBe("false"), + ); + }); + it("keeps a focused section below every sticky header", () => { render( diff --git a/libs/@hashintel/petrinaut/src/ui/components/section.tsx b/libs/@hashintel/petrinaut/src/ui/components/section.tsx index 3ea16ba033b..b25d32b7b3a 100644 --- a/libs/@hashintel/petrinaut/src/ui/components/section.tsx +++ b/libs/@hashintel/petrinaut/src/ui/components/section.tsx @@ -1,10 +1,13 @@ import { Collapsible } from "@ark-ui/react/collapsible"; -import { type ReactNode, use } from "react"; +import { type ReactNode, use, useState } from "react"; import { Button, HelpTooltip } from "@hashintel/ds-components"; import { css, cx } from "@hashintel/ds-helpers/css"; import { UserSettingsContext } from "../../react/state/user-settings-context"; +import { useFocusHeader } from "../worksheet/use-focus-member"; +import { PointerHelpTooltip } from "./pointer-help-tooltip"; +import { StackedSectionHeader, StackedSections } from "./stacked-sections"; // -- SectionList (wrapper) -------------------------------------------------- @@ -16,21 +19,38 @@ const sectionListStyle = css({ "& > *:not(:last-child)": { borderBottomWidth: "[1px]", borderBottomStyle: "solid", - borderBottomColor: "neutral.a20", + borderBottomColor: "neutral.a40", }, }); interface SectionListProps { children: ReactNode; + stacked?: boolean; } -export const SectionList = ({ children }: SectionListProps) => ( -
{children}
-); +export const SectionList = ({ children, stacked }: SectionListProps) => + stacked ? ( + {children} + ) : ( +
{children}
+ ); // -- Section ----------------------------------------------------------------- +const stackedSectionStyle = css({ + display: "contents", + "&[data-state=open] + [data-section] > [data-stack-anchor]": { + marginTop: "2", + }, +}); +const stackedContentStyle = css({ + paddingBottom: "2", + borderBottom: "[1px solid {colors.neutral.a20}]", + "&[data-part=content]": { overflow: "visible" }, +}); + const sectionStyle = css({ + "&:has([data-stacked-sections]) > [data-part=content]": { overflow: "clip" }, display: "flex", flexDirection: "column", position: "relative", @@ -60,6 +80,8 @@ const fillHeightSectionStyle = css({ }); const headerStyle = css({ + "&[data-stack-header]": { paddingY: "1" }, + "&[data-stack-header]::after": { display: "none" }, position: "sticky", top: "[0]", zIndex: "[2]", @@ -121,14 +143,6 @@ const triggerButtonStyle = css({ }, }); -// Collapsible.Trigger injects aria-expanded, which the ds Button renders as -// pressed — strip it so the trigger keeps the resting ghost look. data-state -// still carries open/closed for the chevron rotation. -const TriggerButton = ({ - "aria-expanded": _ariaExpanded, - ...props -}: React.ComponentProps) =>
+ {renderStickyBand && ( +
{renderStickyBand()}
+ )} + + ); + if (collapsible) { return ( onOpenChange(details.open) : undefined - } + open={expanded} + onOpenChange={(details) => setExpanded(details.open)} lazyMount={unmountOnCollapse} unmountOnExit={unmountOnCollapse} - className={cx(sectionStyle, className)} + data-section + className={cx(stacked ? stackedSectionStyle : sectionStyle, className)} > -
-
- {headerLeft} - {renderHeaderAction &&
{renderHeaderAction()}
} - - - + {stacked ? ( + + {renderHeaderContent} + + ) : ( +
+ {renderHeaderContent()}
- {renderStickyBand && ( -
{renderStickyBand()}
- )} -
+ )}
{children}
@@ -261,6 +315,7 @@ export const Section = ({ return (
-
+
- {headerLeft} + {headerLeft()} {renderHeaderAction &&
{renderHeaderAction()}
}
{renderStickyBand && ( diff --git a/libs/@hashintel/petrinaut/src/ui/components/spreadsheet.tsx b/libs/@hashintel/petrinaut/src/ui/components/spreadsheet.tsx index d3fb8cc181a..1641b76f300 100644 --- a/libs/@hashintel/petrinaut/src/ui/components/spreadsheet.tsx +++ b/libs/@hashintel/petrinaut/src/ui/components/spreadsheet.tsx @@ -49,7 +49,7 @@ const tableContainerStyle = css({ borderWidth: "[1px]", borderStyle: "solid", borderColor: "neutral.bd.subtle", - borderRadius: "sm", + borderRadius: "md", overflow: "auto", width: "[100%]", backgroundColor: "neutral.s10", diff --git a/libs/@hashintel/petrinaut/src/ui/components/stacked-sections.test.tsx b/libs/@hashintel/petrinaut/src/ui/components/stacked-sections.test.tsx new file mode 100644 index 00000000000..cfb1ec48798 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/components/stacked-sections.test.tsx @@ -0,0 +1,134 @@ +/** @vitest-environment jsdom */ +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import { afterEach, expect, it, vi } from "vitest"; + +import { StackedSectionHeader, StackedSections } from "./stacked-sections"; + +const sectionNames = ["Variables", "Parameters", "Initial state"]; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + vi.unstubAllGlobals(); +}); + +it("stacks previous headings and returns to the selected section in its own scroll area", async () => { + vi.stubGlobal( + "ResizeObserver", + class { + observe() {} + disconnect() {} + }, + ); + vi.stubGlobal("matchMedia", () => ({ matches: true })); + const scrollTo = vi.fn(); + vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation( + function measureSection(this: HTMLElement) { + const scroller = this.closest('[data-testid="scroll-area"]'); + const headers = Array.from( + scroller?.querySelectorAll("[data-stack-header]") ?? [], + ); + const header = this.hasAttribute("data-stack-anchor") + ? this.nextElementSibling + : this; + const index = headers.indexOf(header as Element); + const top = + index < 0 ? 100 : 112 + index * 200 - (scroller?.scrollTop ?? 0); + return { + top, + bottom: top + 32, + height: 32, + left: 0, + right: 400, + width: 400, + x: 0, + y: top, + toJSON: () => ({}), + }; + }, + ); + render( +
+ + {sectionNames.map((name) => ( +
+ + {(title) => title(name)} + +
{name} content
+
+ ))} +
+
, + ); + const variables = screen.getByRole("button", { name: "Back to Variables" }); + const parameters = screen.getByRole("button", { + name: /(?:Back|Go) to Parameters/, + }); + const initialState = screen.getByRole("button", { + name: /(?:Back|Go) to Initial state/, + }); + expect(variables.tabIndex).toBe(-1); + const scroller = screen.getByTestId("scroll-area"); + scroller.scrollTo = scrollTo; + Object.defineProperty(scroller, "clientHeight", { value: 220 }); + Object.defineProperty(scroller, "scrollHeight", { + get: () => + 450 + + Number.parseFloat( + scroller.querySelector("[data-stack-tail]")?.style + .height ?? "0", + ), + }); + fireEvent.scroll(scroller); + await waitFor(() => + expect(parameters.getAttribute("aria-label")).toBe("Go to Parameters"), + ); + expect(initialState.tabIndex).toBe(0); + await waitFor(() => + expect( + scroller.querySelector("[data-stack-tail]")?.style.height, + ).toBe("118px"), + ); + expect( + parameters.closest("[data-stack-header]")?.style.bottom, + ).toBe("16px"); + expect( + initialState.closest("[data-stack-header]")?.style.bottom, + ).toBe("-16px"); + fireEvent.click(initialState); + expect(scrollTo).toHaveBeenLastCalledWith({ top: 348, behavior: "instant" }); + scroller.scrollTop = 400; + fireEvent.scroll(scroller); + await waitFor(() => expect(variables.tabIndex).toBe(0)); + expect(parameters.tabIndex).toBe(0); + expect(initialState.tabIndex).toBe(-1); + expect( + initialState.closest("[data-stack-header]")?.style.top, + ).toBe("52px"); + fireEvent.click(parameters); + expect(scrollTo).toHaveBeenCalledWith({ top: 180, behavior: "instant" }); + const parameterFade = parameters + .closest("[data-stack-header]") + ?.querySelector('[data-scroll-fade="top"]'); + scroller.scrollTop = 183; + fireEvent.scroll(scroller); + await waitFor(() => expect(parameterFade?.style.opacity).toBe("1")); + scroller.scrollTop = 180.5; + fireEvent.scroll(scroller); + await waitFor(() => expect(parameterFade?.style.opacity).toBe("0")); + fireEvent.click(variables); + expect(scrollTo).toHaveBeenLastCalledWith({ top: 12, behavior: "instant" }); + scroller.scrollTop = 0; + fireEvent.scroll(scroller); + await waitFor(() => expect(variables.tabIndex).toBe(-1)); +}); diff --git a/libs/@hashintel/petrinaut/src/ui/components/stacked-sections.tsx b/libs/@hashintel/petrinaut/src/ui/components/stacked-sections.tsx new file mode 100644 index 00000000000..e18fdb983d5 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/components/stacked-sections.tsx @@ -0,0 +1,350 @@ +import { + createContext, + use, + useId, + useLayoutEffect, + useRef, + useState, +} from "react"; + +import { css, cx } from "@hashintel/ds-helpers/css"; + +import { UserSettingsContext } from "../../react/state/user-settings-context"; +import { FocusControls } from "../worksheet/focus-controls"; +import { ScrollFade } from "./scroll-fade"; + +interface HeaderPosition { + id: string; + top: number; + inactive: boolean; + upcoming: boolean; + bottom: number; + topFade: boolean; + bottomFade: boolean; +} + +const StackContext = createContext<{ + positions: HeaderPosition[]; + jump: (id: string) => void; +} | null>(null); + +const stackStyle = css({ + display: "flex", + flexDirection: "column", + minWidth: "[0]", +}); +const headerStyle = css({ + position: "sticky", + zIndex: "[3]", + backgroundColor: "neutral.s00", + flexShrink: "0", + "&[data-inactive] > :not([data-scroll-fade])": { opacity: "[0.6]" }, +}); +const animatedStyle = css({ + "& > *": { transition: "[opacity 160ms ease]" }, + "@media (prefers-reduced-motion: reduce)": { + "& > *": { transition: "[none]" }, + }, +}); +const anchorStyle = css({ + height: "[0]", + flexShrink: "0", + pointerEvents: "none", +}); +const sectionSpacingStyle = css({ marginTop: "3" }); +const titleButtonStyle = css({ + color: "[inherit]", + font: "inherit", + textAlign: "left", + border: "none", + padding: "[0]", + background: "[transparent]", + cursor: "pointer", + borderRadius: "xs", + "&[tabindex='-1']": { cursor: "default" }, + _focusVisible: { + outline: "[2px solid {colors.blue.s70}]", + outlineOffset: "[2px]", + }, +}); + +const scrollContentTop = (element: HTMLElement) => { + const paddingTop = Number.parseFloat(getComputedStyle(element).paddingTop); + return ( + element.getBoundingClientRect().top + + element.clientTop + + (Number.isFinite(paddingTop) ? paddingTop : 0) + ); +}; + +export const StackedSections: React.FC<{ + children: React.ReactNode; + className?: string; +}> = ({ children, className }) => { + const rootRef = useRef(null); + const scrollRef = useRef(null); + const [positions, setPositions] = useState([]); + const [trailingSpace, setTrailingSpace] = useState(0); + const { showAnimations } = use(UserSettingsContext); + + useLayoutEffect(() => { + const root = rootRef.current; + if (!root) { + return; + } + let scroller: HTMLElement | null = root; + while ( + scroller && + !/(auto|scroll)/.test(getComputedStyle(scroller).overflowY) + ) { + scroller = scroller.parentElement; + } + if (!scroller) { + return; + } + scrollRef.current = scroller; + let frame = 0; + const update = () => { + const headers = Array.from( + root.querySelectorAll("[data-stack-header]"), + ).filter((header) => header.closest("[data-stacked-sections]") === root); + let top = -( + Number.parseFloat(getComputedStyle(scroller).paddingTop) || 0 + ); + // A drawer's containing section may already pin its own header above this stack. + let parent = root.parentElement; + while (parent && parent !== scroller) { + if (parent.hasAttribute("data-section")) { + const header = parent.querySelector( + ":scope > [data-section-header]", + ); + if (header) { + top += header.getBoundingClientRect().height; + } + } + parent = parent.parentElement; + } + const scrollTop = scrollContentTop(scroller); + let active = 0; + const paddingBottom = + Number.parseFloat(getComputedStyle(scroller).paddingBottom) || 0; + const scrollBottom = + scroller.getBoundingClientRect().top + + scroller.clientTop + + scroller.clientHeight; + const next = headers + .map((header, index) => { + const id = header.dataset.stackHeader ?? ""; + const anchor = header.previousElementSibling; + const position = { + id, + top, + inactive: false, + upcoming: false, + bottom: 0, + topFade: false, + bottomFade: false, + }; + if ( + anchor && + anchor.getBoundingClientRect().top <= scrollTop + top + 1 + ) { + active = index; + } + top += header.getBoundingClientRect().height; + return position; + }) + .map((position, index) => ({ ...position, inactive: index < active })); + let bottom = 0; + let firstUpcoming = -1; + for (let index = next.length - 1; index >= 0; index--) { + const position = next[index]; + const header = headers[index]; + if (!position || !header) { + continue; + } + position.bottom = bottom - paddingBottom; + const height = header.getBoundingClientRect().height; + const anchorTop = + header.previousElementSibling?.getBoundingClientRect().top ?? 0; + position.upcoming = + index > active && anchorTop + height > scrollBottom - bottom + 1; + if (position.upcoming) { + firstUpcoming = index; + } + bottom += height; + } + const activePosition = next[active]; + if (activePosition) { + const anchorTop = + headers[active]?.previousElementSibling?.getBoundingClientRect() + .top ?? 0; + activePosition.topFade = anchorTop < scrollTop + activePosition.top - 2; + } + const upcomingPosition = next[firstUpcoming]; + if (upcomingPosition) { + upcomingPosition.bottomFade = true; + } + const lastPosition = next.at(-1); + const lastAnchor = headers.at(-1)?.previousElementSibling; + const tail = root.querySelector( + ":scope > [data-stack-tail]", + ); + if (lastPosition && lastAnchor && tail && scroller.clientHeight > 0) { + const currentSpace = Number.parseFloat(tail.style.height) || 0; + const targetScroll = + scroller.scrollTop + + lastAnchor.getBoundingClientRect().top - + scrollTop - + lastPosition.top; + const availableScroll = + scroller.scrollHeight - scroller.clientHeight - currentSpace; + setTrailingSpace( + Math.max(0, Math.ceil(targetScroll - availableScroll)), + ); + } + setPositions((previous) => + previous.length === next.length && + previous.every((position, index) => { + const candidate = next[index]; + return ( + candidate?.id === position.id && + candidate.top === position.top && + candidate.inactive === position.inactive && + candidate.upcoming === position.upcoming && + candidate.bottom === position.bottom && + candidate.topFade === position.topFade && + candidate.bottomFade === position.bottomFade + ); + }) + ? previous + : next, + ); + }; + const schedule = () => { + cancelAnimationFrame(frame); + frame = requestAnimationFrame(update); + }; + const resize = new ResizeObserver(schedule); + resize.observe(root); + resize.observe(scroller); + const mutations = new MutationObserver(schedule); + mutations.observe(root, { childList: true, subtree: true }); + scroller.addEventListener("scroll", schedule, { passive: true }); + update(); + return () => { + cancelAnimationFrame(frame); + resize.disconnect(); + mutations.disconnect(); + scroller.removeEventListener("scroll", schedule); + scrollRef.current = null; + }; + }, []); + + const jump = (id: string) => { + const root = rootRef.current; + const scroller = scrollRef.current; + const anchor = Array.from( + root?.querySelectorAll("[data-stack-anchor]") ?? [], + ).find((element) => element.dataset.stackAnchor === id); + if (!scroller || !anchor) { + return; + } + const top = positions.find((position) => position.id === id)?.top ?? 0; + scroller.scrollTo({ + top: + scroller.scrollTop + + anchor.getBoundingClientRect().top - + scrollContentTop(scroller) - + top, + behavior: + showAnimations && + !window.matchMedia("(prefers-reduced-motion: reduce)").matches + ? "smooth" + : "instant", + }); + }; + + return ( + +
+ {children} + + + ); +}; + +export const StackedSectionHeader: React.FC<{ + className?: string; + spaceBefore?: boolean; + children: ( + title: (text: string, className?: string) => React.ReactNode, + ) => React.ReactNode; +}> = ({ className, spaceBefore, children }) => { + const id = useId(); + const stack = use(StackContext); + const position = stack?.positions.find((entry) => entry.id === id); + const { showAnimations } = use(UserSettingsContext); + const title = (text: string, titleClassName?: string) => + stack ? ( + + + + ) : ( + {text} + ); + return ( + <> +