libpetri 0.3.2
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/README.md +121 -0
- package/dist/chunk-FN773SSE.js +87 -0
- package/dist/chunk-FN773SSE.js.map +1 -0
- package/dist/chunk-VQ4XMJTD.js +107 -0
- package/dist/chunk-VQ4XMJTD.js.map +1 -0
- package/dist/export/index.d.ts +153 -0
- package/dist/export/index.js +411 -0
- package/dist/export/index.js.map +1 -0
- package/dist/index.d.ts +498 -0
- package/dist/index.js +1972 -0
- package/dist/index.js.map +1 -0
- package/dist/petri-net-C3Jy5HCt.d.ts +543 -0
- package/dist/verification/index.d.ts +505 -0
- package/dist/verification/index.js +1201 -0
- package/dist/verification/index.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# libpetri
|
|
2
|
+
|
|
3
|
+
TypeScript implementation of a **Coloured Time Petri Net** (CTPN) engine with bitmap-based execution, formal verification via Z3, and DOT/Graphviz visualization.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── core/ # Net definition: places, transitions, arcs, timing, output specs
|
|
10
|
+
├── runtime/ # Async bitmap-based executor, marking, compiled net
|
|
11
|
+
├── event/ # Event store and net event types (discriminated union)
|
|
12
|
+
├── export/ # DOT (Graphviz) diagram exporter
|
|
13
|
+
└── verification/ # SMT-based property verification (Z3)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Core (`src/core/`)
|
|
17
|
+
|
|
18
|
+
Immutable net definitions with typed, colored tokens.
|
|
19
|
+
|
|
20
|
+
| Type | Description |
|
|
21
|
+
|------|-------------|
|
|
22
|
+
| `Place<T>` | Typed token container (phantom type for compile-time safety) |
|
|
23
|
+
| `EnvironmentPlace<T>` | External event injection point |
|
|
24
|
+
| `Transition` | Arc specs, timing, priority, guards, action binding |
|
|
25
|
+
| `PetriNet` | Immutable net definition; `bindActions()` separates structure from runtime behavior |
|
|
26
|
+
| `Out` | Discriminated union for output specs: `and`, `xor`, `place`, `timeout`, `forward-input` |
|
|
27
|
+
| `In` | Input arc specs with cardinality: `one`, `exactly`, `all`, `at-least` |
|
|
28
|
+
| `Timing` | TPN firing intervals: `immediate`, `deadline`, `delayed`, `window`, `exact` |
|
|
29
|
+
| `TransitionAction` | `(ctx: TransitionContext) => Promise<void>` — async action bound to a transition |
|
|
30
|
+
|
|
31
|
+
### Runtime (`src/runtime/`)
|
|
32
|
+
|
|
33
|
+
Async single-threaded executor using bitmap-based enablement tracking.
|
|
34
|
+
|
|
35
|
+
| Type | Description |
|
|
36
|
+
|------|-------------|
|
|
37
|
+
| `BitmapNetExecutor` | Main executor — dirty-set tracking, priority scheduling, deadline enforcement |
|
|
38
|
+
| `CompiledNet` | Precomputed bitmap masks and reverse indices for O(W) enablement checks |
|
|
39
|
+
| `Marking` | Mutable FIFO token state per place |
|
|
40
|
+
|
|
41
|
+
Key performance features:
|
|
42
|
+
- `Uint32Array` bitmaps for place marking and transition dirty sets
|
|
43
|
+
- Kernighan's bit-trick for dirty set iteration
|
|
44
|
+
- Pre-allocated buffers to reduce GC pressure
|
|
45
|
+
- Precomputed reverse index (place → affected transitions)
|
|
46
|
+
|
|
47
|
+
### Event (`src/event/`)
|
|
48
|
+
|
|
49
|
+
Observable execution events as a discriminated union (`NetEvent`).
|
|
50
|
+
|
|
51
|
+
Event types: `execution-started`, `execution-completed`, `transition-enabled`, `transition-started`, `transition-completed`, `transition-failed`, `transition-timed-out`, `action-timed-out`, `token-added`, `token-removed`, `log-message`, `marking-snapshot`.
|
|
52
|
+
|
|
53
|
+
`InMemoryEventStore` captures events; `noopEventStore()` is a zero-cost singleton for production.
|
|
54
|
+
|
|
55
|
+
### Export (`src/export/`)
|
|
56
|
+
|
|
57
|
+
`dotExport(net, config)` generates DOT (Graphviz) diagram syntax with proper Petri net visual conventions, including arc types (inhibitor, read, reset), timing annotations, and priority labels.
|
|
58
|
+
|
|
59
|
+
### Verification (`src/verification/`)
|
|
60
|
+
|
|
61
|
+
SMT-based formal verification using Z3. Encodes the Petri net as an integer linear program and checks reachability properties.
|
|
62
|
+
|
|
63
|
+
Supported properties:
|
|
64
|
+
- **Deadlock freedom** — no reachable state where all transitions are disabled
|
|
65
|
+
- **Mutual exclusion** — two places never both hold tokens simultaneously
|
|
66
|
+
- **Place bounds** — token count in a place never exceeds a limit
|
|
67
|
+
- **Unreachability** — a marking is never reachable
|
|
68
|
+
|
|
69
|
+
Also computes **P-invariants** (Farkas variant) and supports IC3/PDR-style incremental verification.
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { place, PetriNet, Transition, one, outPlace, tokenOf, BitmapNetExecutor } from 'libpetri';
|
|
75
|
+
|
|
76
|
+
// Define places
|
|
77
|
+
const input = place<string>('input');
|
|
78
|
+
const output = place<string>('output');
|
|
79
|
+
|
|
80
|
+
// Define transition
|
|
81
|
+
const process = Transition.builder('process')
|
|
82
|
+
.inputs(one(input))
|
|
83
|
+
.outputs(outPlace(output))
|
|
84
|
+
.action(async (ctx) => {
|
|
85
|
+
const value = ctx.input(input);
|
|
86
|
+
ctx.output(output, value.toUpperCase());
|
|
87
|
+
})
|
|
88
|
+
.build();
|
|
89
|
+
|
|
90
|
+
// Build net
|
|
91
|
+
const net = PetriNet.builder('Example').transition(process).build();
|
|
92
|
+
|
|
93
|
+
// Execute
|
|
94
|
+
const executor = new BitmapNetExecutor(
|
|
95
|
+
net,
|
|
96
|
+
new Map([[input, [tokenOf('hello')]]]),
|
|
97
|
+
);
|
|
98
|
+
const marking = await executor.run();
|
|
99
|
+
console.log(marking.peekTokens(output)); // [Token { value: 'HELLO' }]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Verification Example
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { SmtVerifier, deadlockFree } from 'libpetri/verification';
|
|
106
|
+
|
|
107
|
+
const result = await SmtVerifier.forNet(net)
|
|
108
|
+
.initialMarking(m => m.tokens(input, 1))
|
|
109
|
+
.property(deadlockFree())
|
|
110
|
+
.verify();
|
|
111
|
+
|
|
112
|
+
console.log(result.verdict); // { type: 'proven', method: 'structural' }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Build & Test
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm run build # Build with tsup
|
|
119
|
+
npm run check # Type-check with tsc --noEmit
|
|
120
|
+
npm test # Run tests with vitest
|
|
121
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// src/core/timing.ts
|
|
2
|
+
var MAX_DURATION_MS = 365 * 100 * 24 * 60 * 60 * 1e3;
|
|
3
|
+
function immediate() {
|
|
4
|
+
return { type: "immediate" };
|
|
5
|
+
}
|
|
6
|
+
function deadline(byMs) {
|
|
7
|
+
if (byMs <= 0) {
|
|
8
|
+
throw new Error(`Deadline must be positive: ${byMs}`);
|
|
9
|
+
}
|
|
10
|
+
return { type: "deadline", byMs };
|
|
11
|
+
}
|
|
12
|
+
function delayed(afterMs) {
|
|
13
|
+
if (afterMs < 0) {
|
|
14
|
+
throw new Error(`Delay must be non-negative: ${afterMs}`);
|
|
15
|
+
}
|
|
16
|
+
return { type: "delayed", afterMs };
|
|
17
|
+
}
|
|
18
|
+
function window(earliestMs, latestMs) {
|
|
19
|
+
if (earliestMs < 0) {
|
|
20
|
+
throw new Error(`Earliest must be non-negative: ${earliestMs}`);
|
|
21
|
+
}
|
|
22
|
+
if (latestMs < earliestMs) {
|
|
23
|
+
throw new Error(`Latest (${latestMs}) must be >= earliest (${earliestMs})`);
|
|
24
|
+
}
|
|
25
|
+
return { type: "window", earliestMs, latestMs };
|
|
26
|
+
}
|
|
27
|
+
function exact(atMs) {
|
|
28
|
+
if (atMs < 0) {
|
|
29
|
+
throw new Error(`Exact time must be non-negative: ${atMs}`);
|
|
30
|
+
}
|
|
31
|
+
return { type: "exact", atMs };
|
|
32
|
+
}
|
|
33
|
+
function earliest(timing) {
|
|
34
|
+
switch (timing.type) {
|
|
35
|
+
case "immediate":
|
|
36
|
+
return 0;
|
|
37
|
+
case "deadline":
|
|
38
|
+
return 0;
|
|
39
|
+
case "delayed":
|
|
40
|
+
return timing.afterMs;
|
|
41
|
+
case "window":
|
|
42
|
+
return timing.earliestMs;
|
|
43
|
+
case "exact":
|
|
44
|
+
return timing.atMs;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function latest(timing) {
|
|
48
|
+
switch (timing.type) {
|
|
49
|
+
case "immediate":
|
|
50
|
+
return MAX_DURATION_MS;
|
|
51
|
+
case "deadline":
|
|
52
|
+
return timing.byMs;
|
|
53
|
+
case "delayed":
|
|
54
|
+
return MAX_DURATION_MS;
|
|
55
|
+
case "window":
|
|
56
|
+
return timing.latestMs;
|
|
57
|
+
case "exact":
|
|
58
|
+
return timing.atMs;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function hasDeadline(timing) {
|
|
62
|
+
switch (timing.type) {
|
|
63
|
+
case "immediate":
|
|
64
|
+
return false;
|
|
65
|
+
case "deadline":
|
|
66
|
+
return true;
|
|
67
|
+
case "delayed":
|
|
68
|
+
return false;
|
|
69
|
+
case "window":
|
|
70
|
+
return true;
|
|
71
|
+
case "exact":
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
MAX_DURATION_MS,
|
|
78
|
+
immediate,
|
|
79
|
+
deadline,
|
|
80
|
+
delayed,
|
|
81
|
+
window,
|
|
82
|
+
exact,
|
|
83
|
+
earliest,
|
|
84
|
+
latest,
|
|
85
|
+
hasDeadline
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=chunk-FN773SSE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/timing.ts"],"sourcesContent":["/**\n * Firing timing specification for transitions.\n *\n * Based on classical Time Petri Net (TPN) semantics:\n * - Transition CANNOT fire before earliest time (lower bound)\n * - Transition MUST fire by deadline OR become disabled (upper bound)\n *\n * All durations are in milliseconds.\n */\nexport type Timing = TimingImmediate | TimingDeadline | TimingDelayed | TimingWindow | TimingExact;\n\nexport interface TimingImmediate {\n readonly type: 'immediate';\n}\n\nexport interface TimingDeadline {\n readonly type: 'deadline';\n /** Deadline in milliseconds. Must be positive. */\n readonly byMs: number;\n}\n\nexport interface TimingDelayed {\n readonly type: 'delayed';\n /** Minimum delay in milliseconds. Must be non-negative. */\n readonly afterMs: number;\n}\n\nexport interface TimingWindow {\n readonly type: 'window';\n /** Earliest firing time in milliseconds. Must be non-negative. */\n readonly earliestMs: number;\n /** Latest firing time in milliseconds. Must be >= earliestMs. */\n readonly latestMs: number;\n}\n\nexport interface TimingExact {\n readonly type: 'exact';\n /** Exact firing time in milliseconds. Must be non-negative. */\n readonly atMs: number;\n}\n\n/** ~100 years in milliseconds, used for \"unconstrained\" intervals. */\nexport const MAX_DURATION_MS = 365 * 100 * 24 * 60 * 60 * 1000;\n\n// ==================== Factory Functions ====================\n\n/** Immediate firing: can fire as soon as enabled, no deadline. [0, inf) */\nexport function immediate(): TimingImmediate {\n return { type: 'immediate' };\n}\n\n/** Immediate with deadline: can fire immediately, must fire by deadline. [0, by] */\nexport function deadline(byMs: number): TimingDeadline {\n if (byMs <= 0) {\n throw new Error(`Deadline must be positive: ${byMs}`);\n }\n return { type: 'deadline', byMs };\n}\n\n/** Delayed firing: must wait, then can fire anytime. [after, inf) */\nexport function delayed(afterMs: number): TimingDelayed {\n if (afterMs < 0) {\n throw new Error(`Delay must be non-negative: ${afterMs}`);\n }\n return { type: 'delayed', afterMs };\n}\n\n/** Time window: can fire within [earliest, latest]. */\nexport function window(earliestMs: number, latestMs: number): TimingWindow {\n if (earliestMs < 0) {\n throw new Error(`Earliest must be non-negative: ${earliestMs}`);\n }\n if (latestMs < earliestMs) {\n throw new Error(`Latest (${latestMs}) must be >= earliest (${earliestMs})`);\n }\n return { type: 'window', earliestMs, latestMs };\n}\n\n/** Exact timing: fires at precisely the specified time. [at, at] */\nexport function exact(atMs: number): TimingExact {\n if (atMs < 0) {\n throw new Error(`Exact time must be non-negative: ${atMs}`);\n }\n return { type: 'exact', atMs };\n}\n\n// ==================== Query Functions ====================\n\n/** Returns the earliest time (ms) the transition can fire after enabling. */\nexport function earliest(timing: Timing): number {\n switch (timing.type) {\n case 'immediate': return 0;\n case 'deadline': return 0;\n case 'delayed': return timing.afterMs;\n case 'window': return timing.earliestMs;\n case 'exact': return timing.atMs;\n }\n}\n\n/** Returns the latest time (ms) by which the transition must fire. */\nexport function latest(timing: Timing): number {\n switch (timing.type) {\n case 'immediate': return MAX_DURATION_MS;\n case 'deadline': return timing.byMs;\n case 'delayed': return MAX_DURATION_MS;\n case 'window': return timing.latestMs;\n case 'exact': return timing.atMs;\n }\n}\n\n/** Returns true if this timing has a finite deadline. */\nexport function hasDeadline(timing: Timing): boolean {\n switch (timing.type) {\n case 'immediate': return false;\n case 'deadline': return true;\n case 'delayed': return false;\n case 'window': return true;\n case 'exact': return true;\n }\n}\n"],"mappings":";AA0CO,IAAM,kBAAkB,MAAM,MAAM,KAAK,KAAK,KAAK;AAKnD,SAAS,YAA6B;AAC3C,SAAO,EAAE,MAAM,YAAY;AAC7B;AAGO,SAAS,SAAS,MAA8B;AACrD,MAAI,QAAQ,GAAG;AACb,UAAM,IAAI,MAAM,8BAA8B,IAAI,EAAE;AAAA,EACtD;AACA,SAAO,EAAE,MAAM,YAAY,KAAK;AAClC;AAGO,SAAS,QAAQ,SAAgC;AACtD,MAAI,UAAU,GAAG;AACf,UAAM,IAAI,MAAM,+BAA+B,OAAO,EAAE;AAAA,EAC1D;AACA,SAAO,EAAE,MAAM,WAAW,QAAQ;AACpC;AAGO,SAAS,OAAO,YAAoB,UAAgC;AACzE,MAAI,aAAa,GAAG;AAClB,UAAM,IAAI,MAAM,kCAAkC,UAAU,EAAE;AAAA,EAChE;AACA,MAAI,WAAW,YAAY;AACzB,UAAM,IAAI,MAAM,WAAW,QAAQ,0BAA0B,UAAU,GAAG;AAAA,EAC5E;AACA,SAAO,EAAE,MAAM,UAAU,YAAY,SAAS;AAChD;AAGO,SAAS,MAAM,MAA2B;AAC/C,MAAI,OAAO,GAAG;AACZ,UAAM,IAAI,MAAM,oCAAoC,IAAI,EAAE;AAAA,EAC5D;AACA,SAAO,EAAE,MAAM,SAAS,KAAK;AAC/B;AAKO,SAAS,SAAS,QAAwB;AAC/C,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AAAa,aAAO;AAAA,IACzB,KAAK;AAAY,aAAO;AAAA,IACxB,KAAK;AAAW,aAAO,OAAO;AAAA,IAC9B,KAAK;AAAU,aAAO,OAAO;AAAA,IAC7B,KAAK;AAAS,aAAO,OAAO;AAAA,EAC9B;AACF;AAGO,SAAS,OAAO,QAAwB;AAC7C,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AAAa,aAAO;AAAA,IACzB,KAAK;AAAY,aAAO,OAAO;AAAA,IAC/B,KAAK;AAAW,aAAO;AAAA,IACvB,KAAK;AAAU,aAAO,OAAO;AAAA,IAC7B,KAAK;AAAS,aAAO,OAAO;AAAA,EAC9B;AACF;AAGO,SAAS,YAAY,QAAyB;AACnD,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK;AAAa,aAAO;AAAA,IACzB,KAAK;AAAY,aAAO;AAAA,IACxB,KAAK;AAAW,aAAO;AAAA,IACvB,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAS,aAAO;AAAA,EACvB;AACF;","names":[]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/core/out.ts
|
|
2
|
+
function and(...children) {
|
|
3
|
+
if (children.length === 0) {
|
|
4
|
+
throw new Error("AND requires at least 1 child");
|
|
5
|
+
}
|
|
6
|
+
return { type: "and", children };
|
|
7
|
+
}
|
|
8
|
+
function andPlaces(...places) {
|
|
9
|
+
return and(...places.map(outPlace));
|
|
10
|
+
}
|
|
11
|
+
function xor(...children) {
|
|
12
|
+
if (children.length < 2) {
|
|
13
|
+
throw new Error("XOR requires at least 2 children");
|
|
14
|
+
}
|
|
15
|
+
return { type: "xor", children };
|
|
16
|
+
}
|
|
17
|
+
function xorPlaces(...places) {
|
|
18
|
+
return xor(...places.map(outPlace));
|
|
19
|
+
}
|
|
20
|
+
function outPlace(p) {
|
|
21
|
+
return { type: "place", place: p };
|
|
22
|
+
}
|
|
23
|
+
function timeout(afterMs, child) {
|
|
24
|
+
if (afterMs <= 0) {
|
|
25
|
+
throw new Error(`Timeout must be positive: ${afterMs}`);
|
|
26
|
+
}
|
|
27
|
+
return { type: "timeout", afterMs, child };
|
|
28
|
+
}
|
|
29
|
+
function timeoutPlace(afterMs, p) {
|
|
30
|
+
return timeout(afterMs, outPlace(p));
|
|
31
|
+
}
|
|
32
|
+
function forwardInput(from, to) {
|
|
33
|
+
return { type: "forward-input", from, to };
|
|
34
|
+
}
|
|
35
|
+
function allPlaces(out) {
|
|
36
|
+
const result = /* @__PURE__ */ new Set();
|
|
37
|
+
collectPlaces(out, result);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
function collectPlaces(out, result) {
|
|
41
|
+
switch (out.type) {
|
|
42
|
+
case "place":
|
|
43
|
+
result.add(out.place);
|
|
44
|
+
break;
|
|
45
|
+
case "forward-input":
|
|
46
|
+
result.add(out.to);
|
|
47
|
+
break;
|
|
48
|
+
case "and":
|
|
49
|
+
case "xor":
|
|
50
|
+
for (const child of out.children) {
|
|
51
|
+
collectPlaces(child, result);
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
case "timeout":
|
|
55
|
+
collectPlaces(out.child, result);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function enumerateBranches(out) {
|
|
60
|
+
switch (out.type) {
|
|
61
|
+
case "place":
|
|
62
|
+
return [/* @__PURE__ */ new Set([out.place])];
|
|
63
|
+
case "forward-input":
|
|
64
|
+
return [/* @__PURE__ */ new Set([out.to])];
|
|
65
|
+
case "and": {
|
|
66
|
+
let result = [/* @__PURE__ */ new Set()];
|
|
67
|
+
for (const child of out.children) {
|
|
68
|
+
result = crossProduct(result, enumerateBranches(child));
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
case "xor": {
|
|
73
|
+
const result = [];
|
|
74
|
+
for (const child of out.children) {
|
|
75
|
+
result.push(...enumerateBranches(child));
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
case "timeout":
|
|
80
|
+
return enumerateBranches(out.child);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function crossProduct(a, b) {
|
|
84
|
+
const result = [];
|
|
85
|
+
for (const setA of a) {
|
|
86
|
+
for (const setB of b) {
|
|
87
|
+
const merged = new Set(setA);
|
|
88
|
+
for (const p of setB) merged.add(p);
|
|
89
|
+
result.push(merged);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
and,
|
|
97
|
+
andPlaces,
|
|
98
|
+
xor,
|
|
99
|
+
xorPlaces,
|
|
100
|
+
outPlace,
|
|
101
|
+
timeout,
|
|
102
|
+
timeoutPlace,
|
|
103
|
+
forwardInput,
|
|
104
|
+
allPlaces,
|
|
105
|
+
enumerateBranches
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=chunk-VQ4XMJTD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/out.ts"],"sourcesContent":["import type { Place } from './place.js';\n\n/**\n * Output specification with explicit split semantics.\n * Supports composite structures (XOR of ANDs, AND of XORs, etc.)\n *\n * - And: ALL children must receive tokens\n * - Xor: EXACTLY ONE child receives token\n * - Place: Leaf node representing a single output place\n * - Timeout: Timeout branch that activates if action exceeds duration\n * - ForwardInput: Forward consumed input to output on timeout\n */\nexport type Out = OutAnd | OutXor | OutPlace | OutTimeout | OutForwardInput;\n\nexport interface OutAnd {\n readonly type: 'and';\n readonly children: readonly Out[];\n}\n\nexport interface OutXor {\n readonly type: 'xor';\n readonly children: readonly Out[];\n}\n\nexport interface OutPlace {\n readonly type: 'place';\n readonly place: Place<any>;\n}\n\nexport interface OutTimeout {\n readonly type: 'timeout';\n /** Timeout duration in milliseconds. */\n readonly afterMs: number;\n readonly child: Out;\n}\n\nexport interface OutForwardInput {\n readonly type: 'forward-input';\n readonly from: Place<any>;\n readonly to: Place<any>;\n}\n\n// ==================== Factory Functions ====================\n\n/**\n * AND-split: all children must receive tokens.\n *\n * @example\n * ```ts\n * // AND of XOR branches: one of (A,B) AND one of (C,D)\n * and(xorPlaces(placeA, placeB), xorPlaces(placeC, placeD))\n *\n * // AND with a fixed place + XOR branch\n * and(outPlace(always), xorPlaces(left, right))\n * ```\n */\nexport function and(...children: Out[]): OutAnd {\n if (children.length === 0) {\n throw new Error('AND requires at least 1 child');\n }\n return { type: 'and', children };\n}\n\n/** AND-split from places: all places must receive tokens. */\nexport function andPlaces(...places: Place<any>[]): OutAnd {\n return and(...places.map(outPlace));\n}\n\n/** XOR-split: exactly one child receives token. */\nexport function xor(...children: Out[]): OutXor {\n if (children.length < 2) {\n throw new Error('XOR requires at least 2 children');\n }\n return { type: 'xor', children };\n}\n\n/** XOR-split from places: exactly one place receives token. */\nexport function xorPlaces(...places: Place<any>[]): OutXor {\n return xor(...places.map(outPlace));\n}\n\n/** Leaf output spec for a single place. */\nexport function outPlace(p: Place<any>): OutPlace {\n return { type: 'place', place: p };\n}\n\n/** Timeout output: activates if action exceeds duration. */\nexport function timeout(afterMs: number, child: Out): OutTimeout {\n if (afterMs <= 0) {\n throw new Error(`Timeout must be positive: ${afterMs}`);\n }\n return { type: 'timeout', afterMs, child };\n}\n\n/** Timeout output pointing to a single place. */\nexport function timeoutPlace(afterMs: number, p: Place<any>): OutTimeout {\n return timeout(afterMs, outPlace(p));\n}\n\n/** Forward consumed input value to output place on timeout. */\nexport function forwardInput(from: Place<any>, to: Place<any>): OutForwardInput {\n return { type: 'forward-input', from, to };\n}\n\n// ==================== Helper Functions ====================\n\n/** Collects all leaf places from this output spec (flattened). */\nexport function allPlaces(out: Out): Set<Place<any>> {\n const result = new Set<Place<any>>();\n collectPlaces(out, result);\n return result;\n}\n\nfunction collectPlaces(out: Out, result: Set<Place<any>>): void {\n switch (out.type) {\n case 'place':\n result.add(out.place);\n break;\n case 'forward-input':\n result.add(out.to);\n break;\n case 'and':\n case 'xor':\n for (const child of out.children) {\n collectPlaces(child, result);\n }\n break;\n case 'timeout':\n collectPlaces(out.child, result);\n break;\n }\n}\n\n/**\n * Enumerates all possible output branches for structural analysis.\n *\n * - AND = single branch containing all child places (Cartesian product)\n * - XOR = one branch per alternative child\n * - Nested = Cartesian product for AND, union for XOR\n */\nexport function enumerateBranches(out: Out): ReadonlyArray<ReadonlySet<Place<any>>> {\n switch (out.type) {\n case 'place':\n return [new Set([out.place])];\n\n case 'forward-input':\n return [new Set<Place<any>>([out.to])];\n\n case 'and': {\n let result: Set<Place<any>>[] = [new Set()];\n for (const child of out.children) {\n result = crossProduct(result, enumerateBranches(child) as Set<Place<any>>[]);\n }\n return result;\n }\n\n case 'xor': {\n const result: Set<Place<any>>[] = [];\n for (const child of out.children) {\n result.push(...(enumerateBranches(child) as Set<Place<any>>[]));\n }\n return result;\n }\n\n case 'timeout':\n return enumerateBranches(out.child);\n }\n}\n\nfunction crossProduct(\n a: Set<Place<any>>[],\n b: ReadonlyArray<ReadonlySet<Place<any>>>,\n): Set<Place<any>>[] {\n const result: Set<Place<any>>[] = [];\n for (const setA of a) {\n for (const setB of b) {\n const merged = new Set<Place<any>>(setA);\n for (const p of setB) merged.add(p);\n result.push(merged);\n }\n }\n return result;\n}\n"],"mappings":";AAwDO,SAAS,OAAO,UAAyB;AAC9C,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACA,SAAO,EAAE,MAAM,OAAO,SAAS;AACjC;AAGO,SAAS,aAAa,QAA8B;AACzD,SAAO,IAAI,GAAG,OAAO,IAAI,QAAQ,CAAC;AACpC;AAGO,SAAS,OAAO,UAAyB;AAC9C,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AACA,SAAO,EAAE,MAAM,OAAO,SAAS;AACjC;AAGO,SAAS,aAAa,QAA8B;AACzD,SAAO,IAAI,GAAG,OAAO,IAAI,QAAQ,CAAC;AACpC;AAGO,SAAS,SAAS,GAAyB;AAChD,SAAO,EAAE,MAAM,SAAS,OAAO,EAAE;AACnC;AAGO,SAAS,QAAQ,SAAiB,OAAwB;AAC/D,MAAI,WAAW,GAAG;AAChB,UAAM,IAAI,MAAM,6BAA6B,OAAO,EAAE;AAAA,EACxD;AACA,SAAO,EAAE,MAAM,WAAW,SAAS,MAAM;AAC3C;AAGO,SAAS,aAAa,SAAiB,GAA2B;AACvE,SAAO,QAAQ,SAAS,SAAS,CAAC,CAAC;AACrC;AAGO,SAAS,aAAa,MAAkB,IAAiC;AAC9E,SAAO,EAAE,MAAM,iBAAiB,MAAM,GAAG;AAC3C;AAKO,SAAS,UAAU,KAA2B;AACnD,QAAM,SAAS,oBAAI,IAAgB;AACnC,gBAAc,KAAK,MAAM;AACzB,SAAO;AACT;AAEA,SAAS,cAAc,KAAU,QAA+B;AAC9D,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK;AACH,aAAO,IAAI,IAAI,KAAK;AACpB;AAAA,IACF,KAAK;AACH,aAAO,IAAI,IAAI,EAAE;AACjB;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,iBAAW,SAAS,IAAI,UAAU;AAChC,sBAAc,OAAO,MAAM;AAAA,MAC7B;AACA;AAAA,IACF,KAAK;AACH,oBAAc,IAAI,OAAO,MAAM;AAC/B;AAAA,EACJ;AACF;AASO,SAAS,kBAAkB,KAAkD;AAClF,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK;AACH,aAAO,CAAC,oBAAI,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC;AAAA,IAE9B,KAAK;AACH,aAAO,CAAC,oBAAI,IAAgB,CAAC,IAAI,EAAE,CAAC,CAAC;AAAA,IAEvC,KAAK,OAAO;AACV,UAAI,SAA4B,CAAC,oBAAI,IAAI,CAAC;AAC1C,iBAAW,SAAS,IAAI,UAAU;AAChC,iBAAS,aAAa,QAAQ,kBAAkB,KAAK,CAAsB;AAAA,MAC7E;AACA,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,OAAO;AACV,YAAM,SAA4B,CAAC;AACnC,iBAAW,SAAS,IAAI,UAAU;AAChC,eAAO,KAAK,GAAI,kBAAkB,KAAK,CAAuB;AAAA,MAChE;AACA,aAAO;AAAA,IACT;AAAA,IAEA,KAAK;AACH,aAAO,kBAAkB,IAAI,KAAK;AAAA,EACtC;AACF;AAEA,SAAS,aACP,GACA,GACmB;AACnB,QAAM,SAA4B,CAAC;AACnC,aAAW,QAAQ,GAAG;AACpB,eAAW,QAAQ,GAAG;AACpB,YAAM,SAAS,IAAI,IAAgB,IAAI;AACvC,iBAAW,KAAK,KAAM,QAAO,IAAI,CAAC;AAClC,aAAO,KAAK,MAAM;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { a as PetriNet } from '../petri-net-C3Jy5HCt.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format-agnostic typed graph model.
|
|
5
|
+
*
|
|
6
|
+
* Consumed by both the DOT renderer and the animation layer. Nodes carry a
|
|
7
|
+
* `semanticId` that maps back to `Place.name` / `Transition.name` — the stable
|
|
8
|
+
* bridge for animation targeting.
|
|
9
|
+
*
|
|
10
|
+
* ID convention: places get `p_` prefix, transitions get `t_` prefix.
|
|
11
|
+
*
|
|
12
|
+
* @module export/graph
|
|
13
|
+
*/
|
|
14
|
+
type RankDir = 'TB' | 'BT' | 'LR' | 'RL';
|
|
15
|
+
type NodeShape = 'circle' | 'doublecircle' | 'box' | 'diamond' | 'ellipse' | 'record';
|
|
16
|
+
type EdgeLineStyle = 'solid' | 'dashed' | 'bold';
|
|
17
|
+
type ArrowHead = 'normal' | 'odot' | 'none' | 'diamond' | 'dot';
|
|
18
|
+
interface GraphNode {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly label: string;
|
|
21
|
+
readonly shape: NodeShape;
|
|
22
|
+
readonly fill: string;
|
|
23
|
+
readonly stroke: string;
|
|
24
|
+
readonly penwidth: number;
|
|
25
|
+
/** Maps back to Place.name or Transition.name for animation targeting. */
|
|
26
|
+
readonly semanticId: string;
|
|
27
|
+
readonly style?: string;
|
|
28
|
+
readonly height?: number;
|
|
29
|
+
readonly width?: number;
|
|
30
|
+
readonly attrs?: Readonly<Record<string, string>>;
|
|
31
|
+
}
|
|
32
|
+
interface GraphEdge {
|
|
33
|
+
readonly from: string;
|
|
34
|
+
readonly to: string;
|
|
35
|
+
readonly label?: string;
|
|
36
|
+
readonly color: string;
|
|
37
|
+
readonly style: EdgeLineStyle;
|
|
38
|
+
readonly arrowhead: ArrowHead;
|
|
39
|
+
readonly penwidth?: number;
|
|
40
|
+
/** The arc type that produced this edge (for semantic queries). */
|
|
41
|
+
readonly arcType: 'input' | 'output' | 'inhibitor' | 'read' | 'reset';
|
|
42
|
+
readonly attrs?: Readonly<Record<string, string>>;
|
|
43
|
+
}
|
|
44
|
+
interface Subgraph {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
readonly label?: string;
|
|
47
|
+
readonly nodes: readonly GraphNode[];
|
|
48
|
+
readonly attrs?: Readonly<Record<string, string>>;
|
|
49
|
+
}
|
|
50
|
+
interface Graph {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly rankdir: RankDir;
|
|
53
|
+
readonly nodes: readonly GraphNode[];
|
|
54
|
+
readonly edges: readonly GraphEdge[];
|
|
55
|
+
readonly subgraphs: readonly Subgraph[];
|
|
56
|
+
readonly graphAttrs: Readonly<Record<string, string>>;
|
|
57
|
+
readonly nodeDefaults: Readonly<Record<string, string>>;
|
|
58
|
+
readonly edgeDefaults: Readonly<Record<string, string>>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Style loader for Petri net visualization.
|
|
63
|
+
*
|
|
64
|
+
* Reads the shared style definition from `spec/petri-net-styles.json` and
|
|
65
|
+
* exposes typed accessors for node and edge visual properties.
|
|
66
|
+
*
|
|
67
|
+
* @module export/styles
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
interface NodeVisual {
|
|
71
|
+
readonly shape: NodeShape;
|
|
72
|
+
readonly fill: string;
|
|
73
|
+
readonly stroke: string;
|
|
74
|
+
readonly penwidth: number;
|
|
75
|
+
readonly style?: string;
|
|
76
|
+
readonly height?: number;
|
|
77
|
+
readonly width?: number;
|
|
78
|
+
}
|
|
79
|
+
interface EdgeVisual {
|
|
80
|
+
readonly color: string;
|
|
81
|
+
readonly style: EdgeLineStyle;
|
|
82
|
+
readonly arrowhead: ArrowHead;
|
|
83
|
+
readonly penwidth?: number;
|
|
84
|
+
}
|
|
85
|
+
interface FontStyle {
|
|
86
|
+
readonly family: string;
|
|
87
|
+
readonly nodeSize: number;
|
|
88
|
+
readonly edgeSize: number;
|
|
89
|
+
}
|
|
90
|
+
interface GraphStyle {
|
|
91
|
+
readonly nodesep: number;
|
|
92
|
+
readonly ranksep: number;
|
|
93
|
+
}
|
|
94
|
+
declare const FONT: FontStyle;
|
|
95
|
+
declare const GRAPH: GraphStyle;
|
|
96
|
+
type NodeCategory = 'place' | 'start' | 'end' | 'environment' | 'transition';
|
|
97
|
+
type EdgeCategory = 'input' | 'output' | 'inhibitor' | 'read' | 'reset';
|
|
98
|
+
/** Returns the visual style for the given node category. */
|
|
99
|
+
declare function nodeStyle(category: NodeCategory): NodeVisual;
|
|
100
|
+
/** Returns the visual style for the given edge/arc type. */
|
|
101
|
+
declare function edgeStyle(arcType: EdgeCategory): EdgeVisual;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Maps a PetriNet definition to a format-agnostic Graph.
|
|
105
|
+
*
|
|
106
|
+
* This is where all the Petri net semantics live. The mapper understands
|
|
107
|
+
* places, transitions, arcs, timing, and priority. It produces a Graph
|
|
108
|
+
* that can be rendered to DOT (or any other format) without Petri net knowledge.
|
|
109
|
+
*
|
|
110
|
+
* @module export/petri-net-mapper
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
interface DotConfig {
|
|
114
|
+
readonly direction: RankDir;
|
|
115
|
+
readonly showTypes: boolean;
|
|
116
|
+
readonly showIntervals: boolean;
|
|
117
|
+
readonly showPriority: boolean;
|
|
118
|
+
readonly environmentPlaces?: ReadonlySet<string>;
|
|
119
|
+
}
|
|
120
|
+
declare const DEFAULT_DOT_CONFIG: DotConfig;
|
|
121
|
+
/** Sanitizes a name for use as a graph node ID. */
|
|
122
|
+
declare function sanitize(name: string): string;
|
|
123
|
+
/** Maps a PetriNet to a format-agnostic Graph. */
|
|
124
|
+
declare function mapToGraph(net: PetriNet, config?: DotConfig): Graph;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Renders a Graph to DOT (Graphviz) string.
|
|
128
|
+
*
|
|
129
|
+
* Pure function with zero Petri net knowledge. Operates solely on the
|
|
130
|
+
* format-agnostic Graph model.
|
|
131
|
+
*
|
|
132
|
+
* @module export/dot-renderer
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
/** Renders a Graph to a DOT string suitable for Graphviz. */
|
|
136
|
+
declare function renderDot(graph: Graph): string;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Convenience function for exporting a PetriNet to DOT format.
|
|
140
|
+
*
|
|
141
|
+
* @module export/dot-exporter
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Exports a PetriNet to DOT (Graphviz) format.
|
|
146
|
+
*
|
|
147
|
+
* @param net the Petri net to export
|
|
148
|
+
* @param config optional export configuration
|
|
149
|
+
* @returns DOT string suitable for rendering with Graphviz
|
|
150
|
+
*/
|
|
151
|
+
declare function dotExport(net: PetriNet, config?: DotConfig): string;
|
|
152
|
+
|
|
153
|
+
export { type ArrowHead, DEFAULT_DOT_CONFIG, type DotConfig, type EdgeCategory, type EdgeLineStyle, type EdgeVisual, FONT, GRAPH, type Graph, type GraphEdge, type GraphNode, type NodeCategory, type NodeShape, type NodeVisual, type RankDir, type Subgraph, dotExport, edgeStyle, mapToGraph, nodeStyle, renderDot, sanitize };
|