libpetri 2.13.0 → 3.0.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/README.md +47 -136
- package/dist/{chunk-7VJ5CYUU.js → chunk-5W6SVYPD.js} +67 -19
- package/dist/chunk-5W6SVYPD.js.map +1 -0
- package/dist/{chunk-JVI5HFRX.js → chunk-VCDOKWVU.js} +2 -2
- package/dist/{chunk-E3ZWB645.js → chunk-YVIPJ6KM.js} +1 -1
- package/dist/chunk-YVIPJ6KM.js.map +1 -0
- package/dist/debug/index.d.ts +2 -2
- package/dist/debug/index.js +2 -2
- package/dist/doclet/index.d.ts +1 -1
- package/dist/doclet/index.js +3 -3
- package/dist/dot-exporter-3STXYK74.js +9 -0
- package/dist/{event-store-DKTenPbC.d.ts → event-store-Df_sAVQ_.d.ts} +1 -1
- package/dist/export/index.d.ts +1 -1
- package/dist/export/index.js +2 -2
- package/dist/index.d.ts +91 -38
- package/dist/index.js +368 -294
- package/dist/index.js.map +1 -1
- package/dist/{petri-net-C3LSY-vm.d.ts → petri-net-UQBBkvLl.d.ts} +24 -29
- package/dist/verification/index.d.ts +6 -5
- package/dist/verification/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-7VJ5CYUU.js.map +0 -1
- package/dist/chunk-E3ZWB645.js.map +0 -1
- package/dist/dot-exporter-SHBYMMJ3.js +0 -9
- /package/dist/{chunk-JVI5HFRX.js.map → chunk-VCDOKWVU.js.map} +0 -0
- /package/dist/{dot-exporter-SHBYMMJ3.js.map → dot-exporter-3STXYK74.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,172 +1,83 @@
|
|
|
1
|
-
# libpetri
|
|
1
|
+
# libpetri for TypeScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/libpetri)
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
5
|
+
[](https://github.com/debe/libpetri/blob/main/LICENSE)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
The TypeScript 6 implementation of libpetri: typed Coloured Time Petri Nets for Promise-based applications, with modular composition, two execution backends, observability, DOT export, and formal verification.
|
|
6
8
|
|
|
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/`)
|
|
9
|
+
See the [project README](https://github.com/debe/libpetri#why-a-petri-net) for the motivation and an order workflow using every arc type, concurrent actions, and timeout routing.
|
|
60
10
|
|
|
61
|
-
|
|
11
|
+
## Install
|
|
62
12
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
### ν-nets (`src/runtime/`)
|
|
13
|
+
```bash
|
|
14
|
+
npm install libpetri
|
|
15
|
+
```
|
|
72
16
|
|
|
73
|
-
|
|
17
|
+
libpetri is ESM-only. The core runtime has no browser-only assumption; optional viewer and documentation entry points declare their own peer dependencies.
|
|
74
18
|
|
|
75
|
-
## Quick
|
|
19
|
+
## Quick start
|
|
76
20
|
|
|
77
21
|
```typescript
|
|
78
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
BitmapNetExecutor, PetriNet, Transition,
|
|
24
|
+
one, outPlace, place, tokenOf,
|
|
25
|
+
} from 'libpetri';
|
|
79
26
|
|
|
80
|
-
// Define places
|
|
81
27
|
const input = place<string>('input');
|
|
82
28
|
const output = place<string>('output');
|
|
83
29
|
|
|
84
|
-
|
|
85
|
-
const process = Transition.builder('process')
|
|
30
|
+
const uppercase = Transition.builder('uppercase')
|
|
86
31
|
.inputs(one(input))
|
|
87
32
|
.outputs(outPlace(output))
|
|
88
33
|
.action(async (ctx) => {
|
|
89
|
-
|
|
90
|
-
ctx.output(output, value.toUpperCase());
|
|
34
|
+
ctx.output(output, ctx.input(input).toUpperCase());
|
|
91
35
|
})
|
|
92
36
|
.build();
|
|
93
37
|
|
|
94
|
-
|
|
95
|
-
const net = PetriNet.builder('Example').transition(process).build();
|
|
96
|
-
|
|
97
|
-
// Execute
|
|
38
|
+
const net = PetriNet.builder('example').transition(uppercase).build();
|
|
98
39
|
const executor = new BitmapNetExecutor(
|
|
99
40
|
net,
|
|
100
41
|
new Map([[input, [tokenOf('hello')]]]),
|
|
101
42
|
);
|
|
102
|
-
const marking = await executor.run();
|
|
103
|
-
console.log(marking.peekTokens(output)); // [Token { value: 'HELLO' }]
|
|
104
|
-
```
|
|
105
43
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
complete `PetriNet` plus a typed `Interface` of **ports** (typed places) and
|
|
110
|
-
**channels** (transitions). Instantiating renames every internal element with a
|
|
111
|
-
`prefix/name`; composing into a host substitutes port places and merges
|
|
112
|
-
channel transitions.
|
|
44
|
+
const result = await executor.run();
|
|
45
|
+
console.log(result.peekFirst(output)?.value); // HELLO
|
|
46
|
+
```
|
|
113
47
|
|
|
114
|
-
|
|
115
|
-
import { PetriNet, SubnetDef, FusionSet, place } from 'libpetri';
|
|
116
|
-
|
|
117
|
-
const items = place<string>('items');
|
|
118
|
-
const slots = place<string>('slots');
|
|
119
|
-
const put = place<string>('put');
|
|
120
|
-
const get = place<string>('get');
|
|
121
|
-
|
|
122
|
-
const buffer = SubnetDef.builder<void>('Buffer')
|
|
123
|
-
.place(items).place(slots)
|
|
124
|
-
.transition(enqueue).transition(dequeue)
|
|
125
|
-
.inputPort('put', put)
|
|
126
|
-
.outputPort('get', get)
|
|
127
|
-
.build();
|
|
48
|
+
## Execution and concurrency
|
|
128
49
|
|
|
129
|
-
|
|
130
|
-
const b2 = buffer.instantiate('b2').bindActions({ enqueue: enqueueImpl, dequeue: fork() });
|
|
50
|
+
`BitmapNetExecutor` is the reference implementation. `PrecompiledNetExecutor` compiles the same net into flat arrays, opcode streams, and priority-partitioned ready queues for production hot paths.
|
|
131
51
|
|
|
132
|
-
|
|
133
|
-
const b1ToB2 = place<string>('b1_to_b2');
|
|
134
|
-
const b2ToConsumer = place<string>('b2_to_c');
|
|
52
|
+
The orchestrator owns the marking and invokes ready actions without awaiting earlier actions first. Promise continuations therefore overlap naturally, while marking updates remain serialized. CPU-heavy synchronous code still blocks the JavaScript event loop; move it to a worker or external service.
|
|
135
53
|
|
|
136
|
-
|
|
137
|
-
.compose(b1, (bind) =>
|
|
138
|
-
bind.bindPort('put', producerToB1).bindPort('get', b1ToB2))
|
|
139
|
-
.compose(b2, (bind) =>
|
|
140
|
-
bind.bindPort('put', b1ToB2).bindPort('get', b2ToConsumer))
|
|
141
|
-
.fuse(FusionSet.of('sharedSlots', b1.port<string>('slots'), b2.port<string>('slots')))
|
|
142
|
-
.build();
|
|
143
|
-
```
|
|
54
|
+
Use places and transitions for coordination rather than hiding concurrency inside `Promise.all`: the net can then visualize, trace, replay, and verify the fan-out and join.
|
|
144
55
|
|
|
145
|
-
|
|
146
|
-
rewrite; channels (`bindChannel`) merge transitions with arc union, timing
|
|
147
|
-
intersection, and caller-wins identity. `FusionSet` declares N-ary place
|
|
148
|
-
equivalence applied **after** all `compose(...)` calls, regardless of
|
|
149
|
-
registration order. Per-instance action overrides are supplied via
|
|
150
|
-
`Instance.bindActions({ originalName: action })`. See
|
|
151
|
-
[`spec/11-modular-composition.md`](../spec/11-modular-composition.md).
|
|
56
|
+
## Package entry points
|
|
152
57
|
|
|
153
|
-
|
|
58
|
+
| Import | Purpose |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `libpetri` | Core model, runtime, events, and composition |
|
|
61
|
+
| `libpetri/export` | DOT mapping and rendering |
|
|
62
|
+
| `libpetri/verification` | Structural analysis, state classes, and Z3-backed SMT verification |
|
|
63
|
+
| `libpetri/debug` | Debug protocol and session archives |
|
|
64
|
+
| `libpetri/viewer` | Interactive DOT/SVG viewer |
|
|
65
|
+
| `libpetri/doclet` | TypeDoc integration |
|
|
154
66
|
|
|
155
|
-
|
|
156
|
-
import { SmtVerifier, deadlockFree } from 'libpetri/verification';
|
|
67
|
+
The model supports input, output, read, inhibitor, and reset arcs; immediate, deadline, delayed, window, and exact timing; AND/XOR/timeout routing; environment places; reusable subnets; place fusion; and ν-net identity correlation.
|
|
157
68
|
|
|
158
|
-
|
|
159
|
-
.initialMarking(m => m.tokens(input, 1))
|
|
160
|
-
.property(deadlockFree())
|
|
161
|
-
.verify();
|
|
69
|
+
## Build and test
|
|
162
70
|
|
|
163
|
-
|
|
71
|
+
```bash
|
|
72
|
+
npm install
|
|
73
|
+
npm run build
|
|
74
|
+
npm run check
|
|
75
|
+
npm test
|
|
164
76
|
```
|
|
165
77
|
|
|
166
|
-
##
|
|
78
|
+
## Project links
|
|
167
79
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
```
|
|
80
|
+
- [Language-agnostic specification](https://github.com/debe/libpetri/blob/main/spec/00-index.md)
|
|
81
|
+
- [Lean soundness and backend-refinement proofs](https://github.com/debe/libpetri/blob/main/lean/README.md)
|
|
82
|
+
- [Changelog](https://github.com/debe/libpetri/blob/main/CHANGELOG.md)
|
|
83
|
+
- [Apache License 2.0](https://github.com/debe/libpetri/blob/main/LICENSE)
|
|
@@ -3,6 +3,55 @@ import {
|
|
|
3
3
|
latest
|
|
4
4
|
} from "./chunk-ATT7U5H5.js";
|
|
5
5
|
|
|
6
|
+
// src/core/in.ts
|
|
7
|
+
function one(place) {
|
|
8
|
+
return { type: "one", place };
|
|
9
|
+
}
|
|
10
|
+
function exactly(count, place) {
|
|
11
|
+
if (count < 1) {
|
|
12
|
+
throw new Error(`count must be >= 1, got: ${count}`);
|
|
13
|
+
}
|
|
14
|
+
return { type: "exactly", place, count };
|
|
15
|
+
}
|
|
16
|
+
function all(place) {
|
|
17
|
+
return { type: "all", place };
|
|
18
|
+
}
|
|
19
|
+
function atLeast(minimum, place) {
|
|
20
|
+
if (minimum < 1) {
|
|
21
|
+
throw new Error(`minimum must be >= 1, got: ${minimum}`);
|
|
22
|
+
}
|
|
23
|
+
return { type: "at-least", place, minimum };
|
|
24
|
+
}
|
|
25
|
+
function requiredCount(spec) {
|
|
26
|
+
switch (spec.type) {
|
|
27
|
+
case "one":
|
|
28
|
+
return 1;
|
|
29
|
+
case "exactly":
|
|
30
|
+
return spec.count;
|
|
31
|
+
case "all":
|
|
32
|
+
return 1;
|
|
33
|
+
case "at-least":
|
|
34
|
+
return spec.minimum;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function consumptionCount(spec, available) {
|
|
38
|
+
if (available < requiredCount(spec)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Cannot consume from '${spec.place.name}': available=${available}, required=${requiredCount(spec)}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
switch (spec.type) {
|
|
44
|
+
case "one":
|
|
45
|
+
return 1;
|
|
46
|
+
case "exactly":
|
|
47
|
+
return spec.count;
|
|
48
|
+
case "all":
|
|
49
|
+
return available;
|
|
50
|
+
case "at-least":
|
|
51
|
+
return available;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
6
55
|
// src/core/out.ts
|
|
7
56
|
function and(...children) {
|
|
8
57
|
if (children.length === 0) {
|
|
@@ -1734,18 +1783,8 @@ function inputRequiredCount(spec) {
|
|
|
1734
1783
|
return spec.minimum;
|
|
1735
1784
|
}
|
|
1736
1785
|
}
|
|
1737
|
-
function inputConsumeCount(spec) {
|
|
1738
|
-
|
|
1739
|
-
case "one":
|
|
1740
|
-
return 1;
|
|
1741
|
-
case "exactly":
|
|
1742
|
-
return spec.count;
|
|
1743
|
-
case "all":
|
|
1744
|
-
return 1;
|
|
1745
|
-
// Analysis: consume minimum (1 token)
|
|
1746
|
-
case "at-least":
|
|
1747
|
-
return spec.minimum;
|
|
1748
|
-
}
|
|
1786
|
+
function inputConsumeCount(spec, available) {
|
|
1787
|
+
return consumptionCount(spec, available);
|
|
1749
1788
|
}
|
|
1750
1789
|
function checkPlaceEnabled(place, required, marking, environmentPlaces, environmentMode) {
|
|
1751
1790
|
if (!environmentPlaces.has(place)) {
|
|
@@ -1763,7 +1802,11 @@ function checkPlaceEnabled(place, required, marking, environmentPlaces, environm
|
|
|
1763
1802
|
function fireTransition(marking, transition, outputPlaces, environmentPlaces, environmentMode) {
|
|
1764
1803
|
const builder = MarkingState.builder().copyFrom(marking);
|
|
1765
1804
|
for (const spec of transition.inputSpecs) {
|
|
1766
|
-
const
|
|
1805
|
+
const available = marking.tokens(spec.place);
|
|
1806
|
+
if (available < inputRequiredCount(spec)) {
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
const toConsume = inputConsumeCount(spec, available);
|
|
1767
1810
|
consumeFromPlace(builder, spec.place, toConsume, environmentPlaces, environmentMode);
|
|
1768
1811
|
}
|
|
1769
1812
|
for (const arc of transition.resets) {
|
|
@@ -2388,11 +2431,11 @@ var NameMarking = class _NameMarking {
|
|
|
2388
2431
|
return syms ? [...syms.keys()] : [];
|
|
2389
2432
|
}
|
|
2390
2433
|
liveSymbols() {
|
|
2391
|
-
const
|
|
2434
|
+
const all2 = /* @__PURE__ */ new Set();
|
|
2392
2435
|
for (const syms of this.perPlace.values()) {
|
|
2393
|
-
for (const s of syms.keys())
|
|
2436
|
+
for (const s of syms.keys()) all2.add(s);
|
|
2394
2437
|
}
|
|
2395
|
-
return [...
|
|
2438
|
+
return [...all2];
|
|
2396
2439
|
}
|
|
2397
2440
|
/**
|
|
2398
2441
|
* Symmetry-canonical key over `colouredOrder` (the finiteness mechanism). Two
|
|
@@ -3075,7 +3118,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3075
3118
|
report.push("=== RESULT ===\n");
|
|
3076
3119
|
report.push(`PROVEN (IC3/PDR): ${propDesc}`);
|
|
3077
3120
|
report.push(" Z3 Spacer proved no reachable state violates the property.");
|
|
3078
|
-
report.push(" NOTE: Verification ignores timing constraints
|
|
3121
|
+
report.push(" NOTE: Verification ignores timing constraints.");
|
|
3079
3122
|
report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
|
|
3080
3123
|
return this.applyNuGuard(buildResult(
|
|
3081
3124
|
{
|
|
@@ -3108,7 +3151,6 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3108
3151
|
}
|
|
3109
3152
|
report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
|
|
3110
3153
|
report.push(" It may be spurious if timing constraints prevent this sequence.");
|
|
3111
|
-
report.push(" JS guards are also ignored in this analysis.");
|
|
3112
3154
|
return this.applyNuGuard(buildResult(
|
|
3113
3155
|
{ type: "violated" },
|
|
3114
3156
|
report.join("\n"),
|
|
@@ -3252,6 +3294,12 @@ function isViolated(result) {
|
|
|
3252
3294
|
}
|
|
3253
3295
|
|
|
3254
3296
|
export {
|
|
3297
|
+
one,
|
|
3298
|
+
exactly,
|
|
3299
|
+
all,
|
|
3300
|
+
atLeast,
|
|
3301
|
+
requiredCount,
|
|
3302
|
+
consumptionCount,
|
|
3255
3303
|
and,
|
|
3256
3304
|
andPlaces,
|
|
3257
3305
|
xor,
|
|
@@ -3307,4 +3355,4 @@ export {
|
|
|
3307
3355
|
isProven,
|
|
3308
3356
|
isViolated
|
|
3309
3357
|
};
|
|
3310
|
-
//# sourceMappingURL=chunk-
|
|
3358
|
+
//# sourceMappingURL=chunk-5W6SVYPD.js.map
|