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 CHANGED
@@ -1,172 +1,83 @@
1
- # libpetri
1
+ # libpetri for TypeScript
2
2
 
3
- TypeScript implementation of a **Coloured Time Petri Net** (CTPN) engine with bitmap-based execution, formal verification via Z3, and DOT/Graphviz visualization.
3
+ [![npm](https://img.shields.io/npm/v/libpetri)](https://www.npmjs.com/package/libpetri)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-6-blue)](https://www.typescriptlang.org/)
5
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](https://github.com/debe/libpetri/blob/main/LICENSE)
4
6
 
5
- ## Architecture
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
- SMT-based formal verification using Z3. Encodes the Petri net as an integer linear program and checks reachability properties.
11
+ ## Install
62
12
 
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
- ### ν-nets (`src/runtime/`)
13
+ ```bash
14
+ npm install libpetri
15
+ ```
72
16
 
73
- Correlated fork/join by identity. A `MatchSpec` declares a `value → NameId` key projection over a transition's input places; a fork mints a fresh opaque name via `ctx.freshName()` into the token payload, and a join consumes only the sibling tokens that project to the same name. The deterministic `(oldest-timestamp, then name)` tie-break is byte-identical across implementations (NU-022), and an incremental matcher keeps correlated-join drain at O(N log N). A bounded `Budget` place is the decidability lever for verification.
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 Start
19
+ ## Quick start
76
20
 
77
21
  ```typescript
78
- import { place, PetriNet, Transition, one, outPlace, tokenOf, BitmapNetExecutor } from 'libpetri';
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
- // Define transition
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
- const value = ctx.input(input);
90
- ctx.output(output, value.toUpperCase());
34
+ ctx.output(output, ctx.input(input).toUpperCase());
91
35
  })
92
36
  .build();
93
37
 
94
- // Build net
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
- ## Modular composition
107
-
108
- Build large nets by reusing open-net fragments. A `SubnetDef` is a structurally
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
- ```typescript
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
- const b1 = buffer.instantiate('b1').bindActions({ enqueue: enqueueImpl, dequeue: fork() });
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
- const producerToB1 = place<string>('p1_to_b1');
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
- const net = PetriNet.builder('Pipeline')
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
- Composition produces a flat `PetriNet`. Ports merge places by structural
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
- ## Verification Example
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
- ```typescript
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
- const result = await SmtVerifier.forNet(net)
159
- .initialMarking(m => m.tokens(input, 1))
160
- .property(deadlockFree())
161
- .verify();
69
+ ## Build and test
162
70
 
163
- console.log(result.verdict); // { type: 'proven', method: 'structural' }
71
+ ```bash
72
+ npm install
73
+ npm run build
74
+ npm run check
75
+ npm test
164
76
  ```
165
77
 
166
- ## Build & Test
78
+ ## Project links
167
79
 
168
- ```bash
169
- npm run build # Build with tsup
170
- npm run check # Type-check with tsc --noEmit
171
- npm test # Run tests with vitest
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
- switch (spec.type) {
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 toConsume = inputConsumeCount(spec);
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 all = /* @__PURE__ */ new Set();
2434
+ const all2 = /* @__PURE__ */ new Set();
2392
2435
  for (const syms of this.perPlace.values()) {
2393
- for (const s of syms.keys()) all.add(s);
2436
+ for (const s of syms.keys()) all2.add(s);
2394
2437
  }
2395
- return [...all];
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 and JS guards.");
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-7VJ5CYUU.js.map
3358
+ //# sourceMappingURL=chunk-5W6SVYPD.js.map