@chronon-sim/engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +19 -0
- package/dist/components/autoscaler.d.ts +47 -0
- package/dist/components/autoscaler.d.ts.map +1 -0
- package/dist/components/autoscaler.js +116 -0
- package/dist/components/autoscaler.js.map +1 -0
- package/dist/components/generator.d.ts +32 -0
- package/dist/components/generator.d.ts.map +1 -0
- package/dist/components/generator.js +124 -0
- package/dist/components/generator.js.map +1 -0
- package/dist/components/index.d.ts +14 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +11 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/message-queue.d.ts +45 -0
- package/dist/components/message-queue.d.ts.map +1 -0
- package/dist/components/message-queue.js +152 -0
- package/dist/components/message-queue.js.map +1 -0
- package/dist/components/pullable.d.ts +24 -0
- package/dist/components/pullable.d.ts.map +1 -0
- package/dist/components/pullable.js +2 -0
- package/dist/components/pullable.js.map +1 -0
- package/dist/components/queue.d.ts +32 -0
- package/dist/components/queue.d.ts.map +1 -0
- package/dist/components/queue.js +88 -0
- package/dist/components/queue.js.map +1 -0
- package/dist/components/rate-limiter.d.ts +24 -0
- package/dist/components/rate-limiter.d.ts.map +1 -0
- package/dist/components/rate-limiter.js +77 -0
- package/dist/components/rate-limiter.js.map +1 -0
- package/dist/components/registry.d.ts +19 -0
- package/dist/components/registry.d.ts.map +1 -0
- package/dist/components/registry.js +43 -0
- package/dist/components/registry.js.map +1 -0
- package/dist/components/retry-policy.d.ts +41 -0
- package/dist/components/retry-policy.d.ts.map +1 -0
- package/dist/components/retry-policy.js +122 -0
- package/dist/components/retry-policy.js.map +1 -0
- package/dist/components/server.d.ts +26 -0
- package/dist/components/server.d.ts.map +1 -0
- package/dist/components/server.js +82 -0
- package/dist/components/server.js.map +1 -0
- package/dist/components/sink.d.ts +12 -0
- package/dist/components/sink.d.ts.map +1 -0
- package/dist/components/sink.js +35 -0
- package/dist/components/sink.js.map +1 -0
- package/dist/components/worker-pool.d.ts +68 -0
- package/dist/components/worker-pool.d.ts.map +1 -0
- package/dist/components/worker-pool.js +224 -0
- package/dist/components/worker-pool.js.map +1 -0
- package/dist/format/definition.d.ts +72 -0
- package/dist/format/definition.d.ts.map +1 -0
- package/dist/format/definition.js +17 -0
- package/dist/format/definition.js.map +1 -0
- package/dist/format/index.d.ts +5 -0
- package/dist/format/index.d.ts.map +1 -0
- package/dist/format/index.js +3 -0
- package/dist/format/index.js.map +1 -0
- package/dist/format/loader.d.ts +19 -0
- package/dist/format/loader.d.ts.map +1 -0
- package/dist/format/loader.js +275 -0
- package/dist/format/loader.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/heap.d.ts +14 -0
- package/dist/kernel/heap.d.ts.map +1 -0
- package/dist/kernel/heap.js +55 -0
- package/dist/kernel/heap.js.map +1 -0
- package/dist/kernel/index.d.ts +3 -0
- package/dist/kernel/index.d.ts.map +1 -0
- package/dist/kernel/index.js +2 -0
- package/dist/kernel/index.js.map +1 -0
- package/dist/kernel/kernel.d.ts +104 -0
- package/dist/kernel/kernel.d.ts.map +1 -0
- package/dist/kernel/kernel.js +236 -0
- package/dist/kernel/kernel.js.map +1 -0
- package/dist/model/entity.d.ts +78 -0
- package/dist/model/entity.d.ts.map +1 -0
- package/dist/model/entity.js +108 -0
- package/dist/model/entity.js.map +1 -0
- package/dist/model/fifo.d.ts +9 -0
- package/dist/model/fifo.d.ts.map +1 -0
- package/dist/model/fifo.js +24 -0
- package/dist/model/fifo.js.map +1 -0
- package/dist/model/index.d.ts +4 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +3 -0
- package/dist/model/index.js.map +1 -0
- package/dist/rng/distributions.d.ts +98 -0
- package/dist/rng/distributions.d.ts.map +1 -0
- package/dist/rng/distributions.js +224 -0
- package/dist/rng/distributions.js.map +1 -0
- package/dist/rng/index.d.ts +4 -0
- package/dist/rng/index.d.ts.map +1 -0
- package/dist/rng/index.js +3 -0
- package/dist/rng/index.js.map +1 -0
- package/dist/rng/rng.d.ts +26 -0
- package/dist/rng/rng.d.ts.map +1 -0
- package/dist/rng/rng.js +101 -0
- package/dist/rng/rng.js.map +1 -0
- package/dist/run/assertions.d.ts +9 -0
- package/dist/run/assertions.d.ts.map +1 -0
- package/dist/run/assertions.js +57 -0
- package/dist/run/assertions.js.map +1 -0
- package/dist/run/index.d.ts +6 -0
- package/dist/run/index.d.ts.map +1 -0
- package/dist/run/index.js +4 -0
- package/dist/run/index.js.map +1 -0
- package/dist/run/results.d.ts +72 -0
- package/dist/run/results.d.ts.map +1 -0
- package/dist/run/results.js +2 -0
- package/dist/run/results.js.map +1 -0
- package/dist/run/run.d.ts +29 -0
- package/dist/run/run.d.ts.map +1 -0
- package/dist/run/run.js +80 -0
- package/dist/run/run.js.map +1 -0
- package/dist/run/simulation.d.ts +34 -0
- package/dist/run/simulation.d.ts.map +1 -0
- package/dist/run/simulation.js +104 -0
- package/dist/run/simulation.js.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/types.d.ts +83 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +19 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema/validate.d.ts +19 -0
- package/dist/schema/validate.d.ts.map +1 -0
- package/dist/schema/validate.js +120 -0
- package/dist/schema/validate.js.map +1 -0
- package/dist/stats/index.d.ts +6 -0
- package/dist/stats/index.d.ts.map +1 -0
- package/dist/stats/index.js +4 -0
- package/dist/stats/index.js.map +1 -0
- package/dist/stats/percentile.d.ts +38 -0
- package/dist/stats/percentile.d.ts.map +1 -0
- package/dist/stats/percentile.js +62 -0
- package/dist/stats/percentile.js.map +1 -0
- package/dist/stats/summary.d.ts +20 -0
- package/dist/stats/summary.d.ts.map +1 -0
- package/dist/stats/summary.js +38 -0
- package/dist/stats/summary.js.map +1 -0
- package/dist/stats/time-weighted.d.ts +31 -0
- package/dist/stats/time-weighted.d.ts.map +1 -0
- package/dist/stats/time-weighted.js +69 -0
- package/dist/stats/time-weighted.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { AssertionDefinition } from "../format/index.js";
|
|
2
|
+
import type { UnitCategory } from "../schema/index.js";
|
|
3
|
+
/** Settings actually used for a run (after CLI overrides). */
|
|
4
|
+
export interface RunSettingsUsed {
|
|
5
|
+
duration: number;
|
|
6
|
+
warmUp: number;
|
|
7
|
+
seed: number;
|
|
8
|
+
replications: number;
|
|
9
|
+
ticksPerSecond: number;
|
|
10
|
+
}
|
|
11
|
+
/** Values of every output in one replication, keyed by output id "<component>.<OutputKey>"; null = undefined (NaN). */
|
|
12
|
+
export interface ReplicationResult {
|
|
13
|
+
index: number;
|
|
14
|
+
/** The seed this replication actually used (derived from the base seed and the index). */
|
|
15
|
+
seed: number;
|
|
16
|
+
eventsProcessed: number;
|
|
17
|
+
outputs: Record<string, number | null>;
|
|
18
|
+
}
|
|
19
|
+
/** Cross-replication summary of one output. */
|
|
20
|
+
export interface OutputSummary {
|
|
21
|
+
/** "<component>.<OutputKey>" */
|
|
22
|
+
id: string;
|
|
23
|
+
component: string;
|
|
24
|
+
componentType: string;
|
|
25
|
+
key: string;
|
|
26
|
+
unit: UnitCategory;
|
|
27
|
+
description: string;
|
|
28
|
+
/** Number of replications with a defined value. */
|
|
29
|
+
n: number;
|
|
30
|
+
mean: number | null;
|
|
31
|
+
stdDev: number | null;
|
|
32
|
+
/** 95% confidence interval for the mean (Student t); null with fewer than 2 replications. */
|
|
33
|
+
ci95: {
|
|
34
|
+
low: number;
|
|
35
|
+
high: number;
|
|
36
|
+
halfWidth: number;
|
|
37
|
+
} | null;
|
|
38
|
+
}
|
|
39
|
+
/** Sampled series for one replication; `values[id][i]` is the value at `times[i]` seconds. */
|
|
40
|
+
export interface TimeSeriesReplication {
|
|
41
|
+
replication: number;
|
|
42
|
+
times: number[];
|
|
43
|
+
values: Record<string, (number | null)[]>;
|
|
44
|
+
}
|
|
45
|
+
export interface TimeSeriesResult {
|
|
46
|
+
interval: number;
|
|
47
|
+
outputs: string[];
|
|
48
|
+
replications: TimeSeriesReplication[];
|
|
49
|
+
}
|
|
50
|
+
/** The outcome of checking one assertion against the results. */
|
|
51
|
+
export interface AssertionResult {
|
|
52
|
+
assertion: AssertionDefinition;
|
|
53
|
+
/** The compared number, or null if it was undefined (e.g. no CI with one replication). */
|
|
54
|
+
actual: number | null;
|
|
55
|
+
passed: boolean;
|
|
56
|
+
/** Human-readable one-liner, e.g. "sink.p99 (mean) = 1.84 <= 2". */
|
|
57
|
+
message: string;
|
|
58
|
+
}
|
|
59
|
+
/** The full, JSON-serialisable result of running a model. Every renderer works from this. */
|
|
60
|
+
export interface RunResults {
|
|
61
|
+
/** Version of this results structure. */
|
|
62
|
+
resultsVersion: 1;
|
|
63
|
+
modelName: string | null;
|
|
64
|
+
settings: RunSettingsUsed;
|
|
65
|
+
/** One entry per component output, in model order. */
|
|
66
|
+
outputs: OutputSummary[];
|
|
67
|
+
replications: ReplicationResult[];
|
|
68
|
+
timeSeries?: TimeSeriesResult;
|
|
69
|
+
/** Present when the model (or the caller) defined assertions. */
|
|
70
|
+
assertions?: AssertionResult[];
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=results.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../src/run/results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,uHAAuH;AACvH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,0FAA0F;IAC1F,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACxC;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,6FAA6F;IAC7F,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC/D;AAED,8FAA8F;AAC9F,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,qBAAqB,EAAE,CAAC;CACvC;AAED,iEAAiE;AACjE,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,mBAAmB,CAAC;IAC/B,0FAA0F;IAC1F,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,6FAA6F;AAC7F,MAAM,WAAW,UAAU;IACzB,yCAAyC;IACzC,cAAc,EAAE,CAAC,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,eAAe,CAAC;IAC1B,sDAAsD;IACtD,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,iEAAiE;IACjE,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"results.js","sourceRoot":"","sources":["../../src/run/results.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ComponentRegistry } from "../components/index.js";
|
|
2
|
+
import type { AssertionDefinition, ModelDefinition, TimeSeriesConfig } from "../format/index.js";
|
|
3
|
+
import type { RunResults } from "./results.js";
|
|
4
|
+
/** Overrides applied on top of the model's own settings. */
|
|
5
|
+
export interface RunOptions {
|
|
6
|
+
seed?: number;
|
|
7
|
+
replications?: number;
|
|
8
|
+
/** Sample these outputs over time, overriding the model's `timeSeries` setting. */
|
|
9
|
+
timeSeries?: TimeSeriesConfig;
|
|
10
|
+
/** Extra assertions to check in addition to the model's own. */
|
|
11
|
+
assertions?: AssertionDefinition[];
|
|
12
|
+
registry?: ComponentRegistry;
|
|
13
|
+
/** Stop with a SimulationLimitError if this many events run at one instant (default 10 million): guards against zero-delay loops. */
|
|
14
|
+
maxEventsPerTick?: number;
|
|
15
|
+
/** Stop with a SimulationLimitError after this many events in one replication (default unlimited). */
|
|
16
|
+
maxEvents?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The time series sampled by default when a caller asks for one but the model configures none:
|
|
20
|
+
* every output flagged `series` (current-value outputs like QueueLength), ~200 samples per run.
|
|
21
|
+
*/
|
|
22
|
+
export declare function defaultTimeSeries(model: ModelDefinition, registry?: ComponentRegistry): TimeSeriesConfig;
|
|
23
|
+
/** Replication `i` runs with a seed derived from the base seed and `i`. */
|
|
24
|
+
export declare function replicationSeed(baseSeed: number, index: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* Run all replications of a validated model and return the structured, JSON-serialisable results.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runModel(model: ModelDefinition, options?: RunOptions): RunResults;
|
|
29
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAIjG,OAAO,KAAK,EAAiB,UAAU,EAAyB,MAAM,cAAc,CAAC;AAGrF,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,gEAAgE;IAChE,UAAU,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,qIAAqI;IACrI,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,GAAE,iBAA2C,GAAG,gBAAgB,CAQjI;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,GAAE,UAAe,GAAG,UAAU,CAoDrF"}
|
package/dist/run/run.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createDefaultRegistry } from "../components/index.js";
|
|
2
|
+
import { deriveSeed } from "../rng/index.js";
|
|
3
|
+
import { summarize } from "../stats/index.js";
|
|
4
|
+
import { evaluateAssertions } from "./assertions.js";
|
|
5
|
+
import { Simulation } from "./simulation.js";
|
|
6
|
+
/**
|
|
7
|
+
* The time series sampled by default when a caller asks for one but the model configures none:
|
|
8
|
+
* every output flagged `series` (current-value outputs like QueueLength), ~200 samples per run.
|
|
9
|
+
*/
|
|
10
|
+
export function defaultTimeSeries(model, registry = createDefaultRegistry()) {
|
|
11
|
+
const outputs = [];
|
|
12
|
+
for (const c of model.components) {
|
|
13
|
+
for (const o of registry.get(c.type)?.schema.outputs ?? []) {
|
|
14
|
+
if (o.series)
|
|
15
|
+
outputs.push(`${c.name}.${o.key}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return { interval: model.settings.duration / 200, outputs };
|
|
19
|
+
}
|
|
20
|
+
/** Replication `i` runs with a seed derived from the base seed and `i`. */
|
|
21
|
+
export function replicationSeed(baseSeed, index) {
|
|
22
|
+
return deriveSeed(baseSeed, index);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Run all replications of a validated model and return the structured, JSON-serialisable results.
|
|
26
|
+
*/
|
|
27
|
+
export function runModel(model, options = {}) {
|
|
28
|
+
const registry = options.registry ?? createDefaultRegistry();
|
|
29
|
+
const seed = options.seed ?? model.settings.seed;
|
|
30
|
+
const replications = options.replications ?? model.settings.replications;
|
|
31
|
+
if (!Number.isInteger(replications) || replications < 1)
|
|
32
|
+
throw new RangeError("replications must be a positive integer");
|
|
33
|
+
const tsConfig = options.timeSeries ?? model.settings.timeSeries ?? null;
|
|
34
|
+
const reps = [];
|
|
35
|
+
const series = [];
|
|
36
|
+
for (let i = 0; i < replications; i++) {
|
|
37
|
+
const sim = new Simulation(model, replicationSeed(seed, i), i, tsConfig, registry, {
|
|
38
|
+
...(options.maxEventsPerTick !== undefined ? { maxEventsPerTick: options.maxEventsPerTick } : {}),
|
|
39
|
+
...(options.maxEvents !== undefined ? { maxEvents: options.maxEvents } : {}),
|
|
40
|
+
});
|
|
41
|
+
const { result, timeSeries } = sim.runToEnd();
|
|
42
|
+
reps.push(result);
|
|
43
|
+
if (timeSeries)
|
|
44
|
+
series.push(timeSeries);
|
|
45
|
+
}
|
|
46
|
+
const outputs = [];
|
|
47
|
+
for (const def of model.components) {
|
|
48
|
+
const schema = registry.get(def.type).schema;
|
|
49
|
+
for (const o of schema.outputs) {
|
|
50
|
+
const id = `${def.name}.${o.key}`;
|
|
51
|
+
const s = summarize(reps.map((r) => r.outputs[id] ?? NaN));
|
|
52
|
+
outputs.push({
|
|
53
|
+
id,
|
|
54
|
+
component: def.name,
|
|
55
|
+
componentType: def.type,
|
|
56
|
+
key: o.key,
|
|
57
|
+
unit: o.unit,
|
|
58
|
+
description: o.description,
|
|
59
|
+
n: s.n,
|
|
60
|
+
mean: s.mean,
|
|
61
|
+
stdDev: s.stdDev,
|
|
62
|
+
ci95: s.ci95,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const { duration, warmUp, ticksPerSecond } = model.settings;
|
|
67
|
+
const results = {
|
|
68
|
+
resultsVersion: 1,
|
|
69
|
+
modelName: model.name ?? null,
|
|
70
|
+
settings: { duration, warmUp, seed, replications, ticksPerSecond },
|
|
71
|
+
outputs,
|
|
72
|
+
replications: reps,
|
|
73
|
+
...(tsConfig ? { timeSeries: { interval: tsConfig.interval, outputs: tsConfig.outputs, replications: series } } : {}),
|
|
74
|
+
};
|
|
75
|
+
const assertions = [...(model.assertions ?? []), ...(options.assertions ?? [])];
|
|
76
|
+
if (assertions.length > 0)
|
|
77
|
+
results.assertions = evaluateAssertions(results, assertions);
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ComponentRegistry } from "../components/index.js";
|
|
2
|
+
import type { ModelDefinition, TimeSeriesConfig } from "../format/index.js";
|
|
3
|
+
import { Kernel, type KernelLimits } from "../kernel/index.js";
|
|
4
|
+
import type { LinkedComponent } from "../model/index.js";
|
|
5
|
+
import type { ReplicationResult, TimeSeriesReplication } from "./results.js";
|
|
6
|
+
/** One instantiated replication of a model: kernel, components, warm-up and sampling wired up. */
|
|
7
|
+
export declare class Simulation {
|
|
8
|
+
readonly seed: number;
|
|
9
|
+
readonly index: number;
|
|
10
|
+
readonly kernel: Kernel;
|
|
11
|
+
/** Components by name, in model order. */
|
|
12
|
+
readonly components: Map<string, LinkedComponent>;
|
|
13
|
+
private readonly registry;
|
|
14
|
+
private readonly model;
|
|
15
|
+
private readonly series;
|
|
16
|
+
private readonly seriesConfig;
|
|
17
|
+
/**
|
|
18
|
+
* @param model a validated model (from `loadModel`)
|
|
19
|
+
* @param seed the seed for this replication
|
|
20
|
+
* @param index replication index (recorded in results)
|
|
21
|
+
* @param timeSeries optional sampling configuration
|
|
22
|
+
* @param limits optional safety limits (see {@link KernelLimits})
|
|
23
|
+
*/
|
|
24
|
+
constructor(model: ModelDefinition, seed: number, index?: number, timeSeries?: TimeSeriesConfig | null, registry?: ComponentRegistry, limits?: KernelLimits);
|
|
25
|
+
/** Read one output by id "<component>.<OutputKey>" (NaN if unknown or undefined). */
|
|
26
|
+
readOutput(id: string): number;
|
|
27
|
+
private recordSample;
|
|
28
|
+
/** Run to the model's duration and collect every output. */
|
|
29
|
+
runToEnd(): {
|
|
30
|
+
result: ReplicationResult;
|
|
31
|
+
timeSeries: TimeSeriesReplication | null;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=simulation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/run/simulation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAY,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAA4B,MAAM,mBAAmB,CAAC;AAEnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE7E,kGAAkG;AAClG,qBAAa,UAAU;IAkBnB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,KAAK;IAlBhB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0CAA0C;IAC1C,QAAQ,CAAC,UAAU,+BAAsC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0B;IAEvD;;;;;;OAMG;gBAED,KAAK,EAAE,eAAe,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,SAAI,EAClB,UAAU,GAAE,gBAAgB,GAAG,IAAW,EAC1C,QAAQ,GAAE,iBAA2C,EACrD,MAAM,GAAE,YAAiB;IAgD3B,qFAAqF;IACrF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM;IAQ9B,OAAO,CAAC,YAAY;IASpB,4DAA4D;IAC5D,QAAQ,IAAI;QAAE,MAAM,EAAE,iBAAiB,CAAC;QAAC,UAAU,EAAE,qBAAqB,GAAG,IAAI,CAAA;KAAE;CAgBpF"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createDefaultRegistry } from "../components/index.js";
|
|
2
|
+
import { Kernel, Priority } from "../kernel/index.js";
|
|
3
|
+
import { Rng } from "../rng/index.js";
|
|
4
|
+
/** One instantiated replication of a model: kernel, components, warm-up and sampling wired up. */
|
|
5
|
+
export class Simulation {
|
|
6
|
+
seed;
|
|
7
|
+
index;
|
|
8
|
+
kernel;
|
|
9
|
+
/** Components by name, in model order. */
|
|
10
|
+
components = new Map();
|
|
11
|
+
registry;
|
|
12
|
+
model;
|
|
13
|
+
series;
|
|
14
|
+
seriesConfig;
|
|
15
|
+
/**
|
|
16
|
+
* @param model a validated model (from `loadModel`)
|
|
17
|
+
* @param seed the seed for this replication
|
|
18
|
+
* @param index replication index (recorded in results)
|
|
19
|
+
* @param timeSeries optional sampling configuration
|
|
20
|
+
* @param limits optional safety limits (see {@link KernelLimits})
|
|
21
|
+
*/
|
|
22
|
+
constructor(model, seed, index = 0, timeSeries = null, registry = createDefaultRegistry(), limits = {}) {
|
|
23
|
+
this.seed = seed;
|
|
24
|
+
this.index = index;
|
|
25
|
+
this.model = model;
|
|
26
|
+
this.registry = registry;
|
|
27
|
+
this.kernel = new Kernel(model.settings.ticksPerSecond, limits);
|
|
28
|
+
let nextId = 0;
|
|
29
|
+
const kernel = this.kernel;
|
|
30
|
+
const ctx = {
|
|
31
|
+
kernel,
|
|
32
|
+
rng: (streamId) => new Rng(seed, streamId),
|
|
33
|
+
createEntity: () => ({ id: nextId++, createdAt: kernel.currentTick, attributes: {} }),
|
|
34
|
+
};
|
|
35
|
+
for (const def of model.components) {
|
|
36
|
+
const cls = registry.get(def.type);
|
|
37
|
+
if (!cls)
|
|
38
|
+
throw new Error(`unknown component type ${def.type}`); // unreachable for validated models
|
|
39
|
+
this.components.set(def.name, new cls(ctx, { name: def.name, stream: def.stream ?? def.name, inputs: def.inputs }));
|
|
40
|
+
}
|
|
41
|
+
for (const def of model.components) {
|
|
42
|
+
const c = this.components.get(def.name);
|
|
43
|
+
for (const [key, target] of Object.entries(def.links)) {
|
|
44
|
+
c.setLink(key, this.components.get(target));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const c of this.components.values())
|
|
48
|
+
c.start();
|
|
49
|
+
const { warmUp, duration } = model.settings;
|
|
50
|
+
if (warmUp > 0) {
|
|
51
|
+
kernel.schedule(kernel.secondsToTicks(warmUp), Priority.HIGHEST, () => {
|
|
52
|
+
for (const c of this.components.values())
|
|
53
|
+
c.resetStatistics();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
this.seriesConfig = timeSeries;
|
|
57
|
+
this.series = timeSeries ? { replication: index, times: [], values: Object.fromEntries(timeSeries.outputs.map((o) => [o, []])) } : null;
|
|
58
|
+
if (timeSeries) {
|
|
59
|
+
const total = Math.floor(duration / timeSeries.interval + 1e-9);
|
|
60
|
+
const sample = (i) => {
|
|
61
|
+
this.recordSample();
|
|
62
|
+
if (i < total) {
|
|
63
|
+
const next = kernel.secondsToTicks((i + 1) * timeSeries.interval);
|
|
64
|
+
kernel.schedule(next - kernel.currentTick, Priority.LOWEST, () => sample(i + 1));
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
kernel.schedule(0, Priority.LOWEST, () => sample(0));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Read one output by id "<component>.<OutputKey>" (NaN if unknown or undefined). */
|
|
71
|
+
readOutput(id) {
|
|
72
|
+
const dot = id.lastIndexOf(".");
|
|
73
|
+
const comp = this.components.get(id.slice(0, dot));
|
|
74
|
+
const def = this.model.components.find((c) => c.name === id.slice(0, dot));
|
|
75
|
+
const spec = def ? this.registry.get(def.type)?.schema.outputs.find((o) => o.key === id.slice(dot + 1)) : undefined;
|
|
76
|
+
return comp && spec ? spec.get(comp) : NaN;
|
|
77
|
+
}
|
|
78
|
+
recordSample() {
|
|
79
|
+
const s = this.series;
|
|
80
|
+
s.times.push(this.kernel.currentSeconds);
|
|
81
|
+
for (const id of this.seriesConfig.outputs) {
|
|
82
|
+
const v = this.readOutput(id);
|
|
83
|
+
s.values[id].push(Number.isFinite(v) ? v : null);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Run to the model's duration and collect every output. */
|
|
87
|
+
runToEnd() {
|
|
88
|
+
this.kernel.runUntil(this.kernel.secondsToTicks(this.model.settings.duration));
|
|
89
|
+
const outputs = {};
|
|
90
|
+
for (const def of this.model.components) {
|
|
91
|
+
const schema = this.registry.get(def.type).schema;
|
|
92
|
+
const comp = this.components.get(def.name);
|
|
93
|
+
for (const o of schema.outputs) {
|
|
94
|
+
const v = o.get(comp);
|
|
95
|
+
outputs[`${def.name}.${o.key}`] = Number.isFinite(v) ? v : null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
result: { index: this.index, seed: this.seed, eventsProcessed: this.kernel.eventsProcessed, outputs },
|
|
100
|
+
timeSeries: this.series,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=simulation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simulation.js","sourceRoot":"","sources":["../../src/run/simulation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { describeSchema } from "./types.js";
|
|
2
|
+
export type { UnitCategory, InputType, InputSpec, OutputSpec, LinkSpec, ComponentSchema, ValidationError, SchemaDescription, } from "./types.js";
|
|
3
|
+
export { validateInputs, asSampler, asRateProfile } from "./validate.js";
|
|
4
|
+
export type { InputValidation, RateSegment } from "./validate.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EACV,YAAY,EACZ,SAAS,EACT,SAAS,EACT,UAAU,EACV,QAAQ,EACR,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACzE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAW5C,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit categories. All time quantities in the JSON format are seconds and all rates are per second,
|
|
3
|
+
* regardless of the model's tick resolution. `cost` is an abstract currency amount (whatever unit
|
|
4
|
+
* the model's prices use).
|
|
5
|
+
*/
|
|
6
|
+
export type UnitCategory = "time" | "rate" | "cost" | "dimensionless";
|
|
7
|
+
/** The kinds of values a component input can take. */
|
|
8
|
+
export type InputType = "number" | "integer" | "boolean" | "string" | "enum" | "sampler" | "rateProfile";
|
|
9
|
+
/** Declares one input of a component. Pure data. */
|
|
10
|
+
export interface InputSpec {
|
|
11
|
+
key: string;
|
|
12
|
+
type: InputType;
|
|
13
|
+
unit: UnitCategory;
|
|
14
|
+
description: string;
|
|
15
|
+
required: boolean;
|
|
16
|
+
/** Value used when the input is omitted. Ignored when `required` or `requiredWhen` applies. */
|
|
17
|
+
default?: unknown;
|
|
18
|
+
/** Inclusive lower bound (number, integer, and constant samplers). */
|
|
19
|
+
min?: number;
|
|
20
|
+
/** Inclusive upper bound (number, integer, and constant samplers). */
|
|
21
|
+
max?: number;
|
|
22
|
+
/** Allowed values when `type` is "enum". */
|
|
23
|
+
options?: readonly string[];
|
|
24
|
+
/** Input is additionally required when another input has the given value. */
|
|
25
|
+
requiredWhen?: {
|
|
26
|
+
input: string;
|
|
27
|
+
equals: unknown;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Declares one output (statistic) of a component. Pure data except for the getter. */
|
|
31
|
+
export interface OutputSpec<T = any> {
|
|
32
|
+
key: string;
|
|
33
|
+
unit: UnitCategory;
|
|
34
|
+
description: string;
|
|
35
|
+
/** Include in the default time series (current-value outputs such as QueueLength). */
|
|
36
|
+
series?: boolean;
|
|
37
|
+
/** Read the value from a live component. Return NaN when undefined (e.g. no observations). */
|
|
38
|
+
get(component: T): number;
|
|
39
|
+
}
|
|
40
|
+
/** Declares a link from one component to another. Pure data. */
|
|
41
|
+
export interface LinkSpec {
|
|
42
|
+
key: string;
|
|
43
|
+
description: string;
|
|
44
|
+
required: boolean;
|
|
45
|
+
/** Role the target component must have (see {@link ComponentSchema.roles}). */
|
|
46
|
+
accepts: string;
|
|
47
|
+
}
|
|
48
|
+
/** The schema of a component class: its inputs, links and outputs. */
|
|
49
|
+
export interface ComponentSchema<T = any> {
|
|
50
|
+
/** Component type name used in model files, e.g. "Queue". */
|
|
51
|
+
type: string;
|
|
52
|
+
description: string;
|
|
53
|
+
/** Roles this component plays, matched against `LinkSpec.accepts` (e.g. "receiver", "queue"). */
|
|
54
|
+
roles: readonly string[];
|
|
55
|
+
inputs: readonly InputSpec[];
|
|
56
|
+
links: readonly LinkSpec[];
|
|
57
|
+
outputs: readonly OutputSpec<T>[];
|
|
58
|
+
}
|
|
59
|
+
/** A structured validation problem. Never a thrown string. */
|
|
60
|
+
export interface ValidationError {
|
|
61
|
+
/** Component name, or null for model-level problems. */
|
|
62
|
+
component: string | null;
|
|
63
|
+
/** Input/link/setting key the problem is about, or null if it concerns the component as a whole. */
|
|
64
|
+
key: string | null;
|
|
65
|
+
message: string;
|
|
66
|
+
}
|
|
67
|
+
/** A schema with the getters stripped: plain JSON, e.g. for SDK type or documentation generation. */
|
|
68
|
+
export interface SchemaDescription {
|
|
69
|
+
type: string;
|
|
70
|
+
description: string;
|
|
71
|
+
roles: readonly string[];
|
|
72
|
+
inputs: readonly InputSpec[];
|
|
73
|
+
links: readonly LinkSpec[];
|
|
74
|
+
outputs: readonly {
|
|
75
|
+
key: string;
|
|
76
|
+
unit: UnitCategory;
|
|
77
|
+
description: string;
|
|
78
|
+
series?: boolean;
|
|
79
|
+
}[];
|
|
80
|
+
}
|
|
81
|
+
/** Convert a schema to plain, JSON-serialisable data. */
|
|
82
|
+
export declare function describeSchema(schema: ComponentSchema): SchemaDescription;
|
|
83
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,eAAe,CAAC;AAEtE,sDAAsD;AACtD,MAAM,MAAM,SAAS,GACjB,QAAQ,GACR,SAAS,GACT,SAAS,GACT,QAAQ,GACR,MAAM,GACN,SAAS,GACT,aAAa,CAAC;AAElB,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,+FAA+F;IAC/F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,6EAA6E;IAC7E,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;CACnD;AAED,uFAAuF;AACvF,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,8FAA8F;IAC9F,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,MAAM,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,GAAG;IACtC,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,iGAAiG;IACjG,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IAC7B,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC3B,OAAO,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;CACnC;AAED,8DAA8D;AAC9D,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oGAAoG;IACpG,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qGAAqG;AACrG,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IAC7B,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC3B,OAAO,EAAE,SAAS;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,YAAY,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;CAChG;AAED,yDAAyD;AACzD,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAgBzE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Convert a schema to plain, JSON-serialisable data. */
|
|
2
|
+
export function describeSchema(schema) {
|
|
3
|
+
return {
|
|
4
|
+
type: schema.type,
|
|
5
|
+
description: schema.description,
|
|
6
|
+
roles: schema.roles,
|
|
7
|
+
inputs: schema.inputs,
|
|
8
|
+
links: schema.links,
|
|
9
|
+
outputs: schema.outputs.map((o) => {
|
|
10
|
+
const d = {
|
|
11
|
+
key: o.key,
|
|
12
|
+
unit: o.unit,
|
|
13
|
+
description: o.description,
|
|
14
|
+
};
|
|
15
|
+
return o.series ? { ...d, series: true } : d;
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAuFA,yDAAyD;AACzD,MAAM,UAAU,cAAc,CAAC,MAAuB;IACpD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAChC,MAAM,CAAC,GAAyC;gBAC9C,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,WAAW,EAAE,CAAC,CAAC,WAAW;aAC3B,CAAC;YACF,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC;KACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type SamplerSpec } from "../rng/index.js";
|
|
2
|
+
import type { ComponentSchema, ValidationError } from "./types.js";
|
|
3
|
+
/** One piecewise-constant segment of a rate profile: from `start` seconds, `rate` arrivals/second. */
|
|
4
|
+
export type RateSegment = readonly [startSeconds: number, ratePerSecond: number];
|
|
5
|
+
/** Result of {@link validateInputs}: normalised values (defaults applied) plus all errors found. */
|
|
6
|
+
export interface InputValidation {
|
|
7
|
+
values: Record<string, unknown>;
|
|
8
|
+
errors: ValidationError[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validate a component's raw `inputs` object against its schema, applying defaults.
|
|
12
|
+
* Collects every problem rather than stopping at the first.
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateInputs(schema: ComponentSchema, raw: unknown, componentName: string): InputValidation;
|
|
15
|
+
/** Narrow a validated sampler input. */
|
|
16
|
+
export declare function asSampler(v: unknown): SamplerSpec;
|
|
17
|
+
/** Narrow a validated rate profile input. */
|
|
18
|
+
export declare function asRateProfile(v: unknown): RateSegment[];
|
|
19
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/schema/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAa,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9E,sGAAsG;AACtG,MAAM,MAAM,WAAW,GAAG,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;AAEjF,oGAAoG;AACpG,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AA+DD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,MAAM,GACpB,eAAe,CA0CjB;AAED,wCAAwC;AACxC,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,WAAW,CAEjD;AAED,6CAA6C;AAC7C,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,CAEvD"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { validateSamplerSpec } from "../rng/index.js";
|
|
2
|
+
function isRecord(v) {
|
|
3
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4
|
+
}
|
|
5
|
+
function finite(v) {
|
|
6
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
7
|
+
}
|
|
8
|
+
function checkBounds(spec, v) {
|
|
9
|
+
if (spec.min !== undefined && v < spec.min)
|
|
10
|
+
return `must be >= ${spec.min}`;
|
|
11
|
+
if (spec.max !== undefined && v > spec.max)
|
|
12
|
+
return `must be <= ${spec.max}`;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
/** Check one raw value against its input spec. Returns an error message or null. */
|
|
16
|
+
function checkValue(spec, v) {
|
|
17
|
+
switch (spec.type) {
|
|
18
|
+
case "number": {
|
|
19
|
+
if (!finite(v))
|
|
20
|
+
return "must be a finite number";
|
|
21
|
+
return checkBounds(spec, v);
|
|
22
|
+
}
|
|
23
|
+
case "integer": {
|
|
24
|
+
if (!finite(v) || !Number.isInteger(v))
|
|
25
|
+
return "must be an integer";
|
|
26
|
+
return checkBounds(spec, v);
|
|
27
|
+
}
|
|
28
|
+
case "boolean":
|
|
29
|
+
return typeof v === "boolean" ? null : "must be true or false";
|
|
30
|
+
case "string":
|
|
31
|
+
return typeof v === "string" ? null : "must be a string";
|
|
32
|
+
case "enum":
|
|
33
|
+
return typeof v === "string" && (spec.options ?? []).includes(v)
|
|
34
|
+
? null
|
|
35
|
+
: `must be one of ${(spec.options ?? []).map((o) => JSON.stringify(o)).join(", ")}`;
|
|
36
|
+
case "sampler": {
|
|
37
|
+
const problems = validateSamplerSpec(v);
|
|
38
|
+
if (problems.length > 0)
|
|
39
|
+
return problems.join("; ");
|
|
40
|
+
if (finite(v))
|
|
41
|
+
return checkBounds(spec, v);
|
|
42
|
+
const c = v;
|
|
43
|
+
if (c.dist === "constant" && typeof c.value === "number")
|
|
44
|
+
return checkBounds(spec, c.value);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
case "rateProfile": {
|
|
48
|
+
if (!Array.isArray(v) || v.length === 0) {
|
|
49
|
+
return "must be a non-empty array of [startSeconds, ratePerSecond] pairs";
|
|
50
|
+
}
|
|
51
|
+
let prev = -Infinity;
|
|
52
|
+
for (let i = 0; i < v.length; i++) {
|
|
53
|
+
const seg = v[i];
|
|
54
|
+
if (!Array.isArray(seg) || seg.length !== 2 || !finite(seg[0]) || !finite(seg[1])) {
|
|
55
|
+
return `segment ${i} must be a [startSeconds, ratePerSecond] pair of numbers`;
|
|
56
|
+
}
|
|
57
|
+
if (seg[0] < 0)
|
|
58
|
+
return `segment ${i}: startSeconds must be >= 0`;
|
|
59
|
+
if (seg[1] < 0)
|
|
60
|
+
return `segment ${i}: ratePerSecond must be >= 0`;
|
|
61
|
+
if (seg[0] <= prev)
|
|
62
|
+
return `segment ${i}: startSeconds must be strictly increasing`;
|
|
63
|
+
prev = seg[0];
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Validate a component's raw `inputs` object against its schema, applying defaults.
|
|
71
|
+
* Collects every problem rather than stopping at the first.
|
|
72
|
+
*/
|
|
73
|
+
export function validateInputs(schema, raw, componentName) {
|
|
74
|
+
const errors = [];
|
|
75
|
+
const values = {};
|
|
76
|
+
const err = (key, message) => errors.push({ component: componentName, key, message });
|
|
77
|
+
if (raw !== undefined && !isRecord(raw)) {
|
|
78
|
+
err("inputs", "must be an object");
|
|
79
|
+
return { values, errors };
|
|
80
|
+
}
|
|
81
|
+
const given = (raw ?? {});
|
|
82
|
+
const known = new Set(schema.inputs.map((i) => i.key));
|
|
83
|
+
for (const key of Object.keys(given)) {
|
|
84
|
+
if (!known.has(key)) {
|
|
85
|
+
err(key, `unknown input for ${schema.type} (known inputs: ${[...known].join(", ")})`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
for (const spec of schema.inputs) {
|
|
89
|
+
const v = given[spec.key];
|
|
90
|
+
if (v === undefined || v === null) {
|
|
91
|
+
const conditional = spec.requiredWhen !== undefined &&
|
|
92
|
+
(given[spec.requiredWhen.input] ?? schema.inputs.find((i) => i.key === spec.requiredWhen.input)?.default) ===
|
|
93
|
+
spec.requiredWhen.equals;
|
|
94
|
+
if (spec.required || conditional) {
|
|
95
|
+
err(spec.key, conditional && !spec.required
|
|
96
|
+
? `required when ${spec.requiredWhen.input} is ${JSON.stringify(spec.requiredWhen.equals)}`
|
|
97
|
+
: "required input is missing");
|
|
98
|
+
}
|
|
99
|
+
else if (spec.default !== undefined) {
|
|
100
|
+
values[spec.key] = spec.default;
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const problem = checkValue(spec, v);
|
|
105
|
+
if (problem)
|
|
106
|
+
err(spec.key, problem);
|
|
107
|
+
else
|
|
108
|
+
values[spec.key] = v;
|
|
109
|
+
}
|
|
110
|
+
return { values, errors };
|
|
111
|
+
}
|
|
112
|
+
/** Narrow a validated sampler input. */
|
|
113
|
+
export function asSampler(v) {
|
|
114
|
+
return v;
|
|
115
|
+
}
|
|
116
|
+
/** Narrow a validated rate profile input. */
|
|
117
|
+
export function asRateProfile(v) {
|
|
118
|
+
return v;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=validate.js.map
|