libpetri 2.7.1 → 2.9.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.
@@ -0,0 +1,2773 @@
1
+ import {
2
+ earliest,
3
+ latest
4
+ } from "./chunk-ATT7U5H5.js";
5
+
6
+ // src/core/out.ts
7
+ function and(...children) {
8
+ if (children.length === 0) {
9
+ throw new Error("AND requires at least 1 child");
10
+ }
11
+ return { type: "and", children };
12
+ }
13
+ function andPlaces(...places) {
14
+ return and(...places.map(outPlace));
15
+ }
16
+ function xor(...children) {
17
+ if (children.length < 2) {
18
+ throw new Error("XOR requires at least 2 children");
19
+ }
20
+ return { type: "xor", children };
21
+ }
22
+ function xorPlaces(...places) {
23
+ return xor(...places.map(outPlace));
24
+ }
25
+ function outPlace(p) {
26
+ return { type: "place", place: p };
27
+ }
28
+ function timeout(afterMs, child) {
29
+ if (afterMs <= 0) {
30
+ throw new Error(`Timeout must be positive: ${afterMs}`);
31
+ }
32
+ return { type: "timeout", afterMs, child };
33
+ }
34
+ function timeoutPlace(afterMs, p) {
35
+ return timeout(afterMs, outPlace(p));
36
+ }
37
+ function forwardInput(from, to) {
38
+ return { type: "forward-input", from, to };
39
+ }
40
+ function allPlaces(out) {
41
+ const result = /* @__PURE__ */ new Set();
42
+ collectPlaces(out, result);
43
+ return result;
44
+ }
45
+ function collectPlaces(out, result) {
46
+ switch (out.type) {
47
+ case "place":
48
+ result.add(out.place);
49
+ break;
50
+ case "forward-input":
51
+ result.add(out.to);
52
+ break;
53
+ case "and":
54
+ case "xor":
55
+ for (const child of out.children) {
56
+ collectPlaces(child, result);
57
+ }
58
+ break;
59
+ case "timeout":
60
+ collectPlaces(out.child, result);
61
+ break;
62
+ }
63
+ }
64
+ function enumerateBranches(out) {
65
+ switch (out.type) {
66
+ case "place":
67
+ return [/* @__PURE__ */ new Set([out.place])];
68
+ case "forward-input":
69
+ return [/* @__PURE__ */ new Set([out.to])];
70
+ case "and": {
71
+ let result = [/* @__PURE__ */ new Set()];
72
+ for (const child of out.children) {
73
+ result = crossProduct(result, enumerateBranches(child));
74
+ }
75
+ return result;
76
+ }
77
+ case "xor": {
78
+ const result = [];
79
+ for (const child of out.children) {
80
+ result.push(...enumerateBranches(child));
81
+ }
82
+ return result;
83
+ }
84
+ case "timeout":
85
+ return enumerateBranches(out.child);
86
+ }
87
+ }
88
+ function crossProduct(a, b) {
89
+ const result = [];
90
+ for (const setA of a) {
91
+ for (const setB of b) {
92
+ const merged = new Set(setA);
93
+ for (const p of setB) merged.add(p);
94
+ result.push(merged);
95
+ }
96
+ }
97
+ return result;
98
+ }
99
+
100
+ // src/verification/marking-state.ts
101
+ var MARKING_STATE_KEY = /* @__PURE__ */ Symbol("MarkingState.internal");
102
+ var MarkingState = class _MarkingState {
103
+ tokenCounts;
104
+ placesByName;
105
+ /** @internal Use {@link MarkingState.builder} or {@link MarkingState.empty} to create instances. */
106
+ constructor(key, tokenCounts, placesByName) {
107
+ if (key !== MARKING_STATE_KEY) throw new Error("Use MarkingState.builder() to create instances");
108
+ this.tokenCounts = tokenCounts;
109
+ this.placesByName = placesByName;
110
+ }
111
+ /** Returns the token count for a place (0 if absent). */
112
+ tokens(place) {
113
+ return this.tokenCounts.get(place.name) ?? 0;
114
+ }
115
+ /** Checks if a place has at least one token. */
116
+ hasTokens(place) {
117
+ return this.tokens(place) > 0;
118
+ }
119
+ /** Checks if any of the given places has tokens. */
120
+ hasTokensInAny(places) {
121
+ for (const p of places) {
122
+ if (this.hasTokens(p)) return true;
123
+ }
124
+ return false;
125
+ }
126
+ /** Returns all places with tokens > 0. */
127
+ placesWithTokens() {
128
+ return [...this.placesByName.values()];
129
+ }
130
+ /** Returns the total number of tokens. */
131
+ totalTokens() {
132
+ let sum = 0;
133
+ for (const count of this.tokenCounts.values()) sum += count;
134
+ return sum;
135
+ }
136
+ /** Checks if no tokens exist anywhere. */
137
+ isEmpty() {
138
+ return this.tokenCounts.size === 0;
139
+ }
140
+ toString() {
141
+ if (this.tokenCounts.size === 0) return "{}";
142
+ const entries = [...this.tokenCounts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => `${name}:${count}`);
143
+ return `{${entries.join(", ")}}`;
144
+ }
145
+ static empty() {
146
+ return new _MarkingState(MARKING_STATE_KEY, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
147
+ }
148
+ static builder() {
149
+ return new MarkingStateBuilder();
150
+ }
151
+ };
152
+ var MarkingStateBuilder = class {
153
+ tokenCounts = /* @__PURE__ */ new Map();
154
+ placesByName = /* @__PURE__ */ new Map();
155
+ /** Sets the token count for a place. */
156
+ tokens(place, count) {
157
+ if (count < 0) throw new Error(`Token count cannot be negative: ${count}`);
158
+ if (count > 0) {
159
+ this.tokenCounts.set(place.name, count);
160
+ this.placesByName.set(place.name, place);
161
+ } else {
162
+ this.tokenCounts.delete(place.name);
163
+ this.placesByName.delete(place.name);
164
+ }
165
+ return this;
166
+ }
167
+ /** Adds tokens to a place. */
168
+ addTokens(place, count) {
169
+ if (count < 0) throw new Error(`Token count cannot be negative: ${count}`);
170
+ if (count > 0) {
171
+ const current = this.tokenCounts.get(place.name) ?? 0;
172
+ this.tokenCounts.set(place.name, current + count);
173
+ this.placesByName.set(place.name, place);
174
+ }
175
+ return this;
176
+ }
177
+ /** Removes tokens from a place. Throws if insufficient. */
178
+ removeTokens(place, count) {
179
+ const current = this.tokenCounts.get(place.name) ?? 0;
180
+ const newCount = current - count;
181
+ if (newCount < 0) {
182
+ throw new Error(
183
+ `Cannot remove ${count} tokens from ${place.name} (has ${current})`
184
+ );
185
+ }
186
+ if (newCount === 0) {
187
+ this.tokenCounts.delete(place.name);
188
+ this.placesByName.delete(place.name);
189
+ } else {
190
+ this.tokenCounts.set(place.name, newCount);
191
+ }
192
+ return this;
193
+ }
194
+ /** Copies all token counts from another marking state. */
195
+ copyFrom(other) {
196
+ for (const p of other.placesWithTokens()) {
197
+ this.tokenCounts.set(p.name, other.tokens(p));
198
+ this.placesByName.set(p.name, p);
199
+ }
200
+ return this;
201
+ }
202
+ build() {
203
+ return new MarkingState(MARKING_STATE_KEY, new Map(this.tokenCounts), new Map(this.placesByName));
204
+ }
205
+ };
206
+
207
+ // src/verification/smt-property.ts
208
+ function deadlockFree() {
209
+ return { type: "deadlock-free" };
210
+ }
211
+ function mutualExclusion(p1, p2) {
212
+ return { type: "mutual-exclusion", p1, p2 };
213
+ }
214
+ function placeBound(place, bound) {
215
+ return { type: "place-bound", place, bound };
216
+ }
217
+ function unreachable(places) {
218
+ return { type: "unreachable", places: new Set(places) };
219
+ }
220
+ function branchPlaceBound(place, bound) {
221
+ return { type: "branch-place-bound", place, bound };
222
+ }
223
+ function joinedOrDeadLettered(pending) {
224
+ return { type: "joined-or-dead-lettered", pending };
225
+ }
226
+ function propertyDescription(prop) {
227
+ switch (prop.type) {
228
+ case "deadlock-free":
229
+ return "Deadlock-freedom";
230
+ case "mutual-exclusion":
231
+ return `Mutual exclusion of ${prop.p1.name} and ${prop.p2.name}`;
232
+ case "place-bound":
233
+ return `Place ${prop.place.name} bounded by ${prop.bound}`;
234
+ case "unreachable":
235
+ return `Unreachability of marking with tokens in {${[...prop.places].map((p) => p.name).join(", ")}}`;
236
+ case "branch-place-bound":
237
+ return `Branch place bound (\u03BD-budget): ${prop.place.name} <= ${prop.bound}`;
238
+ case "joined-or-dead-lettered":
239
+ return `Joined-or-dead-lettered: ${prop.pending.name} = 0 at quiescence`;
240
+ }
241
+ }
242
+
243
+ // src/verification/encoding/flat-transition.ts
244
+ function flatTransition(name, source, branchIndex, preVector, postVector, inhibitorPlaces, readPlaces, resetPlaces, consumeAll) {
245
+ return {
246
+ name,
247
+ source,
248
+ branchIndex,
249
+ preVector,
250
+ postVector,
251
+ inhibitorPlaces,
252
+ readPlaces,
253
+ resetPlaces,
254
+ consumeAll
255
+ };
256
+ }
257
+
258
+ // src/verification/analysis/environment-analysis-mode.ts
259
+ function alwaysAvailable() {
260
+ return { type: "always-available" };
261
+ }
262
+ function bounded(maxTokens) {
263
+ if (maxTokens < 0) throw new Error("maxTokens must be non-negative");
264
+ return { type: "bounded", maxTokens };
265
+ }
266
+ function ignore() {
267
+ return { type: "ignore" };
268
+ }
269
+
270
+ // src/verification/encoding/net-flattener.ts
271
+ function flatten(net, environmentPlaces = /* @__PURE__ */ new Set(), environmentMode = alwaysAvailable()) {
272
+ const allPlacesSet = /* @__PURE__ */ new Map();
273
+ for (const p of net.places) {
274
+ allPlacesSet.set(p.name, p);
275
+ }
276
+ for (const t of net.transitions) {
277
+ for (const inSpec of t.inputSpecs) {
278
+ allPlacesSet.set(inSpec.place.name, inSpec.place);
279
+ }
280
+ if (t.outputSpec !== null) {
281
+ for (const p of allPlaces(t.outputSpec)) {
282
+ allPlacesSet.set(p.name, p);
283
+ }
284
+ }
285
+ for (const arc of t.inhibitors) allPlacesSet.set(arc.place.name, arc.place);
286
+ for (const arc of t.reads) allPlacesSet.set(arc.place.name, arc.place);
287
+ for (const arc of t.resets) allPlacesSet.set(arc.place.name, arc.place);
288
+ }
289
+ const places = [...allPlacesSet.values()].sort((a, b) => a.name.localeCompare(b.name));
290
+ const placeIndex = /* @__PURE__ */ new Map();
291
+ for (let i = 0; i < places.length; i++) {
292
+ placeIndex.set(places[i].name, i);
293
+ }
294
+ const environmentBounds = /* @__PURE__ */ new Map();
295
+ const environmentInjection = /* @__PURE__ */ new Map();
296
+ switch (environmentMode.type) {
297
+ case "always-available":
298
+ for (const ep of environmentPlaces) {
299
+ environmentInjection.set(ep.place.name, null);
300
+ }
301
+ break;
302
+ case "bounded":
303
+ for (const ep of environmentPlaces) {
304
+ environmentBounds.set(ep.place.name, environmentMode.maxTokens);
305
+ environmentInjection.set(ep.place.name, environmentMode.maxTokens);
306
+ }
307
+ break;
308
+ case "ignore":
309
+ break;
310
+ }
311
+ const n = places.length;
312
+ const flatTransitions = [];
313
+ for (const transition of net.transitions) {
314
+ const branches = enumerateOutputBranches(transition);
315
+ for (let branchIdx = 0; branchIdx < branches.length; branchIdx++) {
316
+ const branchPlaces = branches[branchIdx];
317
+ const name = branches.length > 1 ? `${transition.name}_b${branchIdx}` : transition.name;
318
+ const preVector = new Array(n).fill(0);
319
+ const consumeAll = new Array(n).fill(false);
320
+ for (const inSpec of transition.inputSpecs) {
321
+ const idx = placeIndex.get(inSpec.place.name);
322
+ if (idx === void 0) continue;
323
+ switch (inSpec.type) {
324
+ case "one":
325
+ preVector[idx] = 1;
326
+ break;
327
+ case "exactly":
328
+ preVector[idx] = inSpec.count;
329
+ break;
330
+ case "all":
331
+ preVector[idx] = 1;
332
+ consumeAll[idx] = true;
333
+ break;
334
+ case "at-least":
335
+ preVector[idx] = inSpec.minimum;
336
+ consumeAll[idx] = true;
337
+ break;
338
+ }
339
+ }
340
+ const postVector = new Array(n).fill(0);
341
+ for (const p of branchPlaces) {
342
+ const idx = placeIndex.get(p.name);
343
+ if (idx !== void 0) {
344
+ postVector[idx] = 1;
345
+ }
346
+ }
347
+ const inhibitorPlaces = transition.inhibitors.map((arc) => placeIndex.get(arc.place.name)).filter((idx) => idx !== void 0);
348
+ const readPlaces = transition.reads.map((arc) => placeIndex.get(arc.place.name)).filter((idx) => idx !== void 0);
349
+ const resetPlaces = transition.resets.map((arc) => placeIndex.get(arc.place.name)).filter((idx) => idx !== void 0);
350
+ flatTransitions.push(flatTransition(
351
+ name,
352
+ transition,
353
+ branches.length > 1 ? branchIdx : -1,
354
+ preVector,
355
+ postVector,
356
+ inhibitorPlaces,
357
+ readPlaces,
358
+ resetPlaces,
359
+ consumeAll
360
+ ));
361
+ }
362
+ }
363
+ return {
364
+ places,
365
+ placeIndex,
366
+ transitions: flatTransitions,
367
+ environmentBounds,
368
+ environmentInjection
369
+ };
370
+ }
371
+ function enumerateOutputBranches(t) {
372
+ if (t.outputSpec !== null) {
373
+ return enumerateBranches(t.outputSpec);
374
+ }
375
+ return [/* @__PURE__ */ new Set()];
376
+ }
377
+
378
+ // src/verification/encoding/incidence-matrix.ts
379
+ var IncidenceMatrix = class _IncidenceMatrix {
380
+ _pre;
381
+ _post;
382
+ _incidence;
383
+ _numTransitions;
384
+ _numPlaces;
385
+ constructor(pre, post, incidence, numTransitions, numPlaces) {
386
+ this._pre = pre;
387
+ this._post = post;
388
+ this._incidence = incidence;
389
+ this._numTransitions = numTransitions;
390
+ this._numPlaces = numPlaces;
391
+ }
392
+ /**
393
+ * Computes the incidence matrix from a FlatNet.
394
+ *
395
+ * Environment-injected places (VER-006) each contribute one extra **injector
396
+ * column** (a virtual transition that produces one token into that place and
397
+ * consumes nothing). This makes P-invariant computation env-aware: a valid
398
+ * invariant `y` must satisfy `y^T·C = 0` for the injector column too, forcing
399
+ * `y[envPlace] = 0` and thereby discarding closed-net conservation laws (e.g.
400
+ * `IN + OUT = const`) that would otherwise vacuously bound an injectable place.
401
+ */
402
+ static from(flatNet) {
403
+ const T = flatNet.transitions.length;
404
+ const P = flatNet.places.length;
405
+ const pre = [];
406
+ const post = [];
407
+ const incidence = [];
408
+ for (let t = 0; t < T; t++) {
409
+ const ft = flatNet.transitions[t];
410
+ const preRow = new Array(P);
411
+ const postRow = new Array(P);
412
+ const incRow = new Array(P);
413
+ for (let p = 0; p < P; p++) {
414
+ preRow[p] = ft.preVector[p];
415
+ postRow[p] = ft.postVector[p];
416
+ incRow[p] = postRow[p] - preRow[p];
417
+ }
418
+ pre.push(preRow);
419
+ post.push(postRow);
420
+ incidence.push(incRow);
421
+ }
422
+ let injectorCount = 0;
423
+ for (const name of flatNet.environmentInjection.keys()) {
424
+ const idx = flatNet.placeIndex.get(name);
425
+ if (idx == null) continue;
426
+ const preRow = new Array(P).fill(0);
427
+ const postRow = new Array(P).fill(0);
428
+ const incRow = new Array(P).fill(0);
429
+ postRow[idx] = 1;
430
+ incRow[idx] = 1;
431
+ pre.push(preRow);
432
+ post.push(postRow);
433
+ incidence.push(incRow);
434
+ injectorCount++;
435
+ }
436
+ return new _IncidenceMatrix(pre, post, incidence, T + injectorCount, P);
437
+ }
438
+ /**
439
+ * Returns C^T (transpose of incidence matrix), dimensions [P][T].
440
+ * Used for P-invariant computation: null space of C^T gives P-invariants.
441
+ */
442
+ transposedIncidence() {
443
+ const ct = [];
444
+ for (let p = 0; p < this._numPlaces; p++) {
445
+ const row = new Array(this._numTransitions);
446
+ for (let t = 0; t < this._numTransitions; t++) {
447
+ row[t] = this._incidence[t][p];
448
+ }
449
+ ct.push(row);
450
+ }
451
+ return ct;
452
+ }
453
+ /** Returns the pre-matrix (tokens consumed). T×P. */
454
+ pre() {
455
+ return this._pre;
456
+ }
457
+ /** Returns the post-matrix (tokens produced). T×P. */
458
+ post() {
459
+ return this._post;
460
+ }
461
+ /** Returns the incidence matrix C[t][p] = post - pre. T×P. */
462
+ incidence() {
463
+ return this._incidence;
464
+ }
465
+ numTransitions() {
466
+ return this._numTransitions;
467
+ }
468
+ numPlaces() {
469
+ return this._numPlaces;
470
+ }
471
+ };
472
+
473
+ // src/verification/invariant/p-invariant.ts
474
+ function pInvariant(weights, constant, support) {
475
+ return { weights, constant, support };
476
+ }
477
+ function pInvariantToString(inv) {
478
+ const parts = [];
479
+ for (const i of inv.support) {
480
+ if (inv.weights[i] !== 1) {
481
+ parts.push(`${inv.weights[i]}*p${i}`);
482
+ } else {
483
+ parts.push(`p${i}`);
484
+ }
485
+ }
486
+ return `PInvariant[${parts.join(" + ")} = ${inv.constant}]`;
487
+ }
488
+
489
+ // src/verification/invariant/p-invariant-computer.ts
490
+ function computePInvariants(matrix, flatNet, initialMarking) {
491
+ const P = matrix.numPlaces();
492
+ const T = matrix.numTransitions();
493
+ if (P === 0 || T === 0) return [];
494
+ const ct = matrix.transposedIncidence();
495
+ const cols = T + P;
496
+ const augmented = [];
497
+ for (let i = 0; i < P; i++) {
498
+ const row = new Array(cols).fill(0);
499
+ for (let j = 0; j < T; j++) {
500
+ row[j] = ct[i][j];
501
+ }
502
+ row[T + i] = 1;
503
+ augmented.push(row);
504
+ }
505
+ let pivotRow = 0;
506
+ for (let col = 0; col < T && pivotRow < P; col++) {
507
+ let pivot = -1;
508
+ for (let row = pivotRow; row < P; row++) {
509
+ if (augmented[row][col] !== 0) {
510
+ pivot = row;
511
+ break;
512
+ }
513
+ }
514
+ if (pivot === -1) continue;
515
+ if (pivot !== pivotRow) {
516
+ const tmp = augmented[pivotRow];
517
+ augmented[pivotRow] = augmented[pivot];
518
+ augmented[pivot] = tmp;
519
+ }
520
+ for (let row = 0; row < P; row++) {
521
+ if (row === pivotRow || augmented[row][col] === 0) continue;
522
+ const a = augmented[pivotRow][col];
523
+ const b = augmented[row][col];
524
+ for (let c = 0; c < cols; c++) {
525
+ augmented[row][c] = a * augmented[row][c] - b * augmented[pivotRow][c];
526
+ }
527
+ normalizeRow(augmented[row], cols);
528
+ }
529
+ pivotRow++;
530
+ }
531
+ const invariants = [];
532
+ for (let row = 0; row < P; row++) {
533
+ let isZero = true;
534
+ for (let col = 0; col < T; col++) {
535
+ if (augmented[row][col] !== 0) {
536
+ isZero = false;
537
+ break;
538
+ }
539
+ }
540
+ if (!isZero) continue;
541
+ const weights = new Array(P);
542
+ let allNonNegative = true;
543
+ let hasPositive = false;
544
+ for (let i = 0; i < P; i++) {
545
+ weights[i] = augmented[row][T + i];
546
+ if (weights[i] < 0) {
547
+ allNonNegative = false;
548
+ break;
549
+ }
550
+ if (weights[i] > 0) hasPositive = true;
551
+ }
552
+ if (!allNonNegative) {
553
+ let allNonPositive = true;
554
+ for (let i = 0; i < P; i++) {
555
+ if (augmented[row][T + i] > 0) {
556
+ allNonPositive = false;
557
+ break;
558
+ }
559
+ }
560
+ if (allNonPositive) {
561
+ for (let i = 0; i < P; i++) {
562
+ weights[i] = -augmented[row][T + i];
563
+ }
564
+ hasPositive = true;
565
+ allNonNegative = true;
566
+ }
567
+ }
568
+ if (!allNonNegative || !hasPositive) continue;
569
+ let g = 0;
570
+ for (const w of weights) {
571
+ if (w > 0) g = gcd(g, w);
572
+ }
573
+ if (g > 1) {
574
+ for (let i = 0; i < P; i++) {
575
+ weights[i] = weights[i] / g;
576
+ }
577
+ }
578
+ const support = /* @__PURE__ */ new Set();
579
+ let constant = 0;
580
+ for (let i = 0; i < P; i++) {
581
+ if (weights[i] !== 0) {
582
+ support.add(i);
583
+ const place = flatNet.places[i];
584
+ constant += weights[i] * initialMarking.tokens(place);
585
+ }
586
+ }
587
+ invariants.push(pInvariant(weights, constant, support));
588
+ }
589
+ return invariants;
590
+ }
591
+ function isCoveredByInvariants(invariants, numPlaces) {
592
+ const covered = new Array(numPlaces).fill(false);
593
+ for (const inv of invariants) {
594
+ for (const idx of inv.support) {
595
+ if (idx < numPlaces) covered[idx] = true;
596
+ }
597
+ }
598
+ return covered.every((c) => c);
599
+ }
600
+ function normalizeRow(row, cols) {
601
+ let g = 0;
602
+ for (let c = 0; c < cols; c++) {
603
+ if (row[c] !== 0) {
604
+ g = gcd(g, Math.abs(row[c]));
605
+ }
606
+ }
607
+ if (g > 1) {
608
+ for (let c = 0; c < cols; c++) {
609
+ row[c] = row[c] / g;
610
+ }
611
+ }
612
+ }
613
+ function gcd(a, b) {
614
+ while (b !== 0) {
615
+ const t = b;
616
+ b = a % b;
617
+ a = t;
618
+ }
619
+ return a;
620
+ }
621
+
622
+ // src/verification/invariant/structural-check.ts
623
+ var MAX_PLACES_FOR_SIPHON_ANALYSIS = 50;
624
+ function structuralCheck(flatNet, initialMarking) {
625
+ const P = flatNet.places.length;
626
+ if (P === 0) {
627
+ return { type: "no-potential-deadlock" };
628
+ }
629
+ if (P > MAX_PLACES_FOR_SIPHON_ANALYSIS) {
630
+ return { type: "inconclusive", reason: `Net has ${P} places, siphon enumeration skipped` };
631
+ }
632
+ const siphons = findMinimalSiphons(flatNet);
633
+ if (siphons.length === 0) {
634
+ return { type: "no-potential-deadlock" };
635
+ }
636
+ for (const siphon of siphons) {
637
+ const trap = findMaximalTrapIn(flatNet, siphon);
638
+ if (trap.size === 0 || !isMarked(trap, flatNet, initialMarking)) {
639
+ return { type: "potential-deadlock", siphon };
640
+ }
641
+ }
642
+ return { type: "no-potential-deadlock" };
643
+ }
644
+ function findMinimalSiphons(flatNet) {
645
+ const P = flatNet.places.length;
646
+ const siphons = [];
647
+ const placeAsOutput = [];
648
+ for (let p = 0; p < P; p++) {
649
+ placeAsOutput.push([]);
650
+ }
651
+ for (let t = 0; t < flatNet.transitions.length; t++) {
652
+ const ft = flatNet.transitions[t];
653
+ for (let p = 0; p < P; p++) {
654
+ if (ft.postVector[p] > 0) {
655
+ placeAsOutput[p].push(t);
656
+ }
657
+ }
658
+ }
659
+ for (let startPlace = 0; startPlace < P; startPlace++) {
660
+ const siphon = computeSiphonContaining(startPlace, flatNet, placeAsOutput);
661
+ if (siphon !== null && siphon.size > 0) {
662
+ let isMinimal = true;
663
+ const toRemove = [];
664
+ for (let i = 0; i < siphons.length; i++) {
665
+ const existing = siphons[i];
666
+ if (setsEqual(existing, siphon)) {
667
+ isMinimal = false;
668
+ break;
669
+ }
670
+ if (isSubsetOf(existing, siphon)) {
671
+ isMinimal = false;
672
+ break;
673
+ }
674
+ if (isSubsetOf(siphon, existing)) {
675
+ toRemove.push(i);
676
+ }
677
+ }
678
+ for (let i = toRemove.length - 1; i >= 0; i--) {
679
+ siphons.splice(toRemove[i], 1);
680
+ }
681
+ if (isMinimal) {
682
+ siphons.push(siphon);
683
+ }
684
+ }
685
+ }
686
+ return siphons;
687
+ }
688
+ function computeSiphonContaining(startPlace, flatNet, placeAsOutput) {
689
+ const siphon = /* @__PURE__ */ new Set();
690
+ siphon.add(startPlace);
691
+ let changed = true;
692
+ while (changed) {
693
+ changed = false;
694
+ const snapshot = [...siphon];
695
+ for (const p of snapshot) {
696
+ for (const t of placeAsOutput[p]) {
697
+ const ft = flatNet.transitions[t];
698
+ let hasInputInSiphon = false;
699
+ for (let q = 0; q < flatNet.places.length; q++) {
700
+ if (ft.preVector[q] > 0 && siphon.has(q)) {
701
+ hasInputInSiphon = true;
702
+ break;
703
+ }
704
+ }
705
+ if (!hasInputInSiphon) {
706
+ let added = false;
707
+ for (let q = 0; q < flatNet.places.length; q++) {
708
+ if (ft.preVector[q] > 0) {
709
+ if (!siphon.has(q)) {
710
+ siphon.add(q);
711
+ changed = true;
712
+ }
713
+ added = true;
714
+ break;
715
+ }
716
+ }
717
+ if (!added) {
718
+ return null;
719
+ }
720
+ }
721
+ }
722
+ }
723
+ }
724
+ return siphon;
725
+ }
726
+ function findMaximalTrapIn(flatNet, places) {
727
+ const trap = new Set(places);
728
+ let changed = true;
729
+ while (changed) {
730
+ changed = false;
731
+ const toRemove = [];
732
+ for (const p of trap) {
733
+ let satisfies = true;
734
+ for (let t = 0; t < flatNet.transitions.length; t++) {
735
+ const ft = flatNet.transitions[t];
736
+ if (ft.preVector[p] > 0) {
737
+ let outputsToTrap = false;
738
+ for (const q of trap) {
739
+ if (ft.postVector[q] > 0) {
740
+ outputsToTrap = true;
741
+ break;
742
+ }
743
+ }
744
+ if (!outputsToTrap) {
745
+ satisfies = false;
746
+ break;
747
+ }
748
+ }
749
+ }
750
+ if (!satisfies) {
751
+ toRemove.push(p);
752
+ }
753
+ }
754
+ if (toRemove.length > 0) {
755
+ for (const p of toRemove) trap.delete(p);
756
+ changed = true;
757
+ }
758
+ }
759
+ return trap;
760
+ }
761
+ function isMarked(placeIndices, flatNet, marking) {
762
+ for (const idx of placeIndices) {
763
+ const place = flatNet.places[idx];
764
+ if (marking.tokens(place) > 0) return true;
765
+ }
766
+ return false;
767
+ }
768
+ function setsEqual(a, b) {
769
+ if (a.size !== b.size) return false;
770
+ for (const v of a) {
771
+ if (!b.has(v)) return false;
772
+ }
773
+ return true;
774
+ }
775
+ function isSubsetOf(sub, sup) {
776
+ if (sub.size > sup.size) return false;
777
+ for (const v of sub) {
778
+ if (!sup.has(v)) return false;
779
+ }
780
+ return true;
781
+ }
782
+
783
+ // src/verification/z3/spacer-runner.ts
784
+ import { init } from "z3-solver";
785
+ async function createSpacerRunner(timeoutMs) {
786
+ const { Context } = await init();
787
+ const ctx = new Context("main");
788
+ const fp = new ctx.Fixedpoint();
789
+ fp.set("engine", "spacer");
790
+ if (timeoutMs > 0) {
791
+ fp.set("timeout", Math.min(timeoutMs, 2147483647));
792
+ }
793
+ async function query(errorExpr, reachableDecl) {
794
+ try {
795
+ const status = await fp.query(errorExpr);
796
+ if (status === "unsat") {
797
+ let invariantFormula = null;
798
+ const levelInvariants = [];
799
+ try {
800
+ const answer = fp.getAnswer();
801
+ if (answer != null) {
802
+ invariantFormula = answer.toString();
803
+ }
804
+ } catch {
805
+ }
806
+ if (reachableDecl != null) {
807
+ try {
808
+ const levels = fp.getNumLevels(reachableDecl);
809
+ for (let i = 0; i < levels; i++) {
810
+ const cover = fp.getCoverDelta(i, reachableDecl);
811
+ if (cover != null && !ctx.isTrue(cover)) {
812
+ levelInvariants.push(`Level ${i}: ${cover.toString()}`);
813
+ }
814
+ }
815
+ } catch {
816
+ }
817
+ }
818
+ return { type: "proven", invariantFormula, levelInvariants };
819
+ }
820
+ if (status === "sat") {
821
+ let answer = null;
822
+ try {
823
+ answer = fp.getAnswer();
824
+ } catch {
825
+ }
826
+ return { type: "violated", answer };
827
+ }
828
+ return { type: "unknown", reason: fp.getReasonUnknown() };
829
+ } catch (e) {
830
+ return { type: "unknown", reason: `Z3 exception: ${e.message ?? e}` };
831
+ }
832
+ }
833
+ function dispose() {
834
+ try {
835
+ fp.release();
836
+ } catch {
837
+ }
838
+ }
839
+ return {
840
+ ctx,
841
+ fp,
842
+ query,
843
+ dispose
844
+ };
845
+ }
846
+
847
+ // src/verification/encoding/flat-net.ts
848
+ function flatNetPlaceCount(net) {
849
+ return net.places.length;
850
+ }
851
+ function flatNetTransitionCount(net) {
852
+ return net.transitions.length;
853
+ }
854
+ function flatNetIndexOf(net, place) {
855
+ return net.placeIndex.get(place.name) ?? -1;
856
+ }
857
+
858
+ // src/verification/z3/smt-encoder.ts
859
+ function encode(ctx, fp, flatNet, initialMarking, property, invariants, sinkPlaces = /* @__PURE__ */ new Set()) {
860
+ const P = flatNet.places.length;
861
+ const Int = ctx.Int;
862
+ const Bool_ = ctx.Bool;
863
+ const intSort = Int.sort();
864
+ const boolSort = Bool_.sort();
865
+ const markingSorts = new Array(P).fill(intSort);
866
+ const reachable = ctx.Function.declare("Reachable", ...markingSorts, boolSort);
867
+ fp.registerRelation(reachable);
868
+ const error = ctx.Function.declare("Error", boolSort);
869
+ fp.registerRelation(error);
870
+ const m0Args = [];
871
+ for (let i = 0; i < P; i++) {
872
+ const tokens = initialMarking.tokens(flatNet.places[i]);
873
+ m0Args.push(Int.val(tokens));
874
+ }
875
+ const initFact = reachable.call(...m0Args);
876
+ fp.addRule(initFact, "init");
877
+ for (let t = 0; t < flatNet.transitions.length; t++) {
878
+ const ft = flatNet.transitions[t];
879
+ encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P);
880
+ }
881
+ for (const [name, bound] of flatNet.environmentInjection) {
882
+ const idx = flatNet.placeIndex.get(name);
883
+ if (idx == null) continue;
884
+ encodeInjectionRule(ctx, fp, reachable, idx, bound, P);
885
+ }
886
+ encodeErrorRule(ctx, fp, reachable, error, flatNet, property, sinkPlaces, P);
887
+ return {
888
+ errorExpr: error.call(),
889
+ reachableDecl: reachable
890
+ };
891
+ }
892
+ function encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P) {
893
+ const Int = ctx.Int;
894
+ const mVars = [];
895
+ const mPrimeVars = [];
896
+ for (let i = 0; i < P; i++) {
897
+ mVars.push(Int.const(`m${i}`));
898
+ mPrimeVars.push(Int.const(`mp${i}`));
899
+ }
900
+ const reachBody = reachable.call(...mVars);
901
+ const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
902
+ const fireRelation = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
903
+ let nonNeg = ctx.Bool.val(true);
904
+ for (let i = 0; i < P; i++) {
905
+ nonNeg = ctx.And(nonNeg, mPrimeVars[i].ge(0));
906
+ }
907
+ const invConstraints = encodeInvariantConstraints(ctx, invariants, mPrimeVars, P);
908
+ let envBounds = ctx.Bool.val(true);
909
+ for (const [name, bound] of flatNet.environmentBounds) {
910
+ const idx = flatNet.placeIndex.get(name);
911
+ if (idx != null) {
912
+ envBounds = ctx.And(envBounds, mPrimeVars[idx].le(bound));
913
+ }
914
+ }
915
+ const body = ctx.And(reachBody, enabled, fireRelation, nonNeg, invConstraints, envBounds);
916
+ const head = reachable.call(...mPrimeVars);
917
+ const allVars = [...mVars, ...mPrimeVars];
918
+ const rule = ctx.Implies(body, head);
919
+ const qRule = ctx.ForAll(allVars, rule);
920
+ fp.addRule(qRule, `t_${ft.name}`);
921
+ }
922
+ function encodeInjectionRule(ctx, fp, reachable, idx, bound, P) {
923
+ const Int = ctx.Int;
924
+ const mVars = [];
925
+ const mPrimeVars = [];
926
+ for (let i = 0; i < P; i++) {
927
+ mVars.push(Int.const(`m${i}`));
928
+ mPrimeVars.push(Int.const(`mp${i}`));
929
+ }
930
+ const reachBody = reachable.call(...mVars);
931
+ let fire = ctx.Bool.val(true);
932
+ for (let i = 0; i < P; i++) {
933
+ if (i === idx) {
934
+ fire = ctx.And(fire, mPrimeVars[i].eq(mVars[i].add(1)));
935
+ } else {
936
+ fire = ctx.And(fire, mPrimeVars[i].eq(mVars[i]));
937
+ }
938
+ }
939
+ const guard = bound === null ? ctx.Bool.val(true) : mVars[idx].lt(bound);
940
+ const body = ctx.And(reachBody, guard, fire);
941
+ const head = reachable.call(...mPrimeVars);
942
+ const qRule = ctx.ForAll([...mVars, ...mPrimeVars], ctx.Implies(body, head));
943
+ fp.addRule(qRule, `env_inject_${idx}`);
944
+ }
945
+ function injectedEnvIndices(flatNet) {
946
+ const out = /* @__PURE__ */ new Map();
947
+ for (const [name, bound] of flatNet.environmentInjection) {
948
+ const idx = flatNet.placeIndex.get(name);
949
+ if (idx != null) out.set(idx, bound);
950
+ }
951
+ return out;
952
+ }
953
+ function encodeEnabled(ctx, ft, flatNet, mVars, P, relaxEnv = false) {
954
+ let result = ctx.Bool.val(true);
955
+ const envInj = relaxEnv ? injectedEnvIndices(flatNet) : void 0;
956
+ for (let p = 0; p < P; p++) {
957
+ const pre = ft.preVector[p];
958
+ if (pre <= 0) continue;
959
+ if (envInj?.has(p)) {
960
+ const bound = envInj.get(p);
961
+ if (bound !== null && pre > bound) return ctx.Bool.val(false);
962
+ continue;
963
+ }
964
+ result = ctx.And(result, mVars[p].ge(pre));
965
+ }
966
+ for (const p of ft.readPlaces) {
967
+ if (envInj?.has(p)) {
968
+ const bound = envInj.get(p);
969
+ if (bound !== null && bound < 1) return ctx.Bool.val(false);
970
+ continue;
971
+ }
972
+ result = ctx.And(result, mVars[p].ge(1));
973
+ }
974
+ for (const p of ft.inhibitorPlaces) {
975
+ result = ctx.And(result, mVars[p].eq(0));
976
+ }
977
+ for (let p = 0; p < P; p++) {
978
+ result = ctx.And(result, mVars[p].ge(0));
979
+ }
980
+ return result;
981
+ }
982
+ function encodeFire(ctx, ft, _flatNet, mVars, mPrimeVars, P) {
983
+ let result = ctx.Bool.val(true);
984
+ for (let p = 0; p < P; p++) {
985
+ const isReset = ft.resetPlaces.includes(p);
986
+ if (isReset || ft.consumeAll[p]) {
987
+ result = ctx.And(result, mPrimeVars[p].eq(ft.postVector[p]));
988
+ } else {
989
+ const delta = ft.postVector[p] - ft.preVector[p];
990
+ if (delta === 0) {
991
+ result = ctx.And(result, mPrimeVars[p].eq(mVars[p]));
992
+ } else {
993
+ result = ctx.And(result, mPrimeVars[p].eq(mVars[p].add(delta)));
994
+ }
995
+ }
996
+ }
997
+ return result;
998
+ }
999
+ function encodeErrorRule(ctx, fp, reachable, error, flatNet, property, sinkPlaces, P) {
1000
+ const Int = ctx.Int;
1001
+ const mVars = [];
1002
+ for (let i = 0; i < P; i++) {
1003
+ mVars.push(Int.const(`em${i}`));
1004
+ }
1005
+ const reachBody = reachable.call(...mVars);
1006
+ const violation = encodePropertyViolation(ctx, flatNet, property, sinkPlaces, mVars, P);
1007
+ const head = error.call();
1008
+ const body = ctx.And(reachBody, violation);
1009
+ const rule = ctx.Implies(body, head);
1010
+ const qRule = ctx.ForAll(mVars, rule);
1011
+ fp.addRule(qRule, `error_${property.type}`);
1012
+ }
1013
+ function encodePropertyViolation(ctx, flatNet, property, sinkPlaces, mVars, P) {
1014
+ switch (property.type) {
1015
+ case "deadlock-free": {
1016
+ const deadlock = encodeDeadlock(ctx, flatNet, mVars, P);
1017
+ if (sinkPlaces.size > 0) {
1018
+ let notAtSink = ctx.Bool.val(true);
1019
+ for (const sink of sinkPlaces) {
1020
+ const idx = flatNetIndexOf(flatNet, sink);
1021
+ if (idx >= 0) {
1022
+ notAtSink = ctx.And(notAtSink, mVars[idx].eq(0));
1023
+ }
1024
+ }
1025
+ return ctx.And(deadlock, notAtSink);
1026
+ }
1027
+ return deadlock;
1028
+ }
1029
+ case "mutual-exclusion": {
1030
+ const idx1 = flatNetIndexOf(flatNet, property.p1);
1031
+ const idx2 = flatNetIndexOf(flatNet, property.p2);
1032
+ if (idx1 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p1.name}`);
1033
+ if (idx2 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p2.name}`);
1034
+ return ctx.And(mVars[idx1].ge(1), mVars[idx2].ge(1));
1035
+ }
1036
+ case "place-bound": {
1037
+ const idx = flatNetIndexOf(flatNet, property.place);
1038
+ if (idx < 0) throw new Error(`PlaceBound references unknown place: ${property.place.name}`);
1039
+ return mVars[idx].gt(property.bound);
1040
+ }
1041
+ case "branch-place-bound": {
1042
+ const idx = flatNetIndexOf(flatNet, property.place);
1043
+ if (idx < 0) throw new Error(`BranchPlaceBound references unknown place: ${property.place.name}`);
1044
+ return mVars[idx].gt(property.bound);
1045
+ }
1046
+ case "joined-or-dead-lettered": {
1047
+ const idx = flatNetIndexOf(flatNet, property.pending);
1048
+ if (idx < 0) return ctx.Bool.val(false);
1049
+ const deadlock = encodeDeadlock(ctx, flatNet, mVars, P);
1050
+ return ctx.And(deadlock, mVars[idx].ge(1));
1051
+ }
1052
+ case "unreachable": {
1053
+ let allMarked = ctx.Bool.val(true);
1054
+ for (const place of property.places) {
1055
+ const idx = flatNetIndexOf(flatNet, place);
1056
+ if (idx >= 0) {
1057
+ allMarked = ctx.And(allMarked, mVars[idx].ge(1));
1058
+ }
1059
+ }
1060
+ return allMarked;
1061
+ }
1062
+ }
1063
+ }
1064
+ function encodeDeadlock(ctx, flatNet, mVars, P) {
1065
+ let deadlock = ctx.Bool.val(true);
1066
+ for (const ft of flatNet.transitions) {
1067
+ const enabled = encodeEnabled(
1068
+ ctx,
1069
+ ft,
1070
+ flatNet,
1071
+ mVars,
1072
+ P,
1073
+ /* relaxEnv */
1074
+ true
1075
+ );
1076
+ deadlock = ctx.And(deadlock, ctx.Not(enabled));
1077
+ }
1078
+ return deadlock;
1079
+ }
1080
+ function encodeInvariantConstraints(ctx, invariants, mVars, P) {
1081
+ let result = ctx.Bool.val(true);
1082
+ for (const inv of invariants) {
1083
+ let sum = ctx.Int.val(0);
1084
+ for (const idx of inv.support) {
1085
+ if (idx < P) {
1086
+ sum = sum.add(mVars[idx].mul(inv.weights[idx]));
1087
+ }
1088
+ }
1089
+ result = ctx.And(result, sum.eq(inv.constant));
1090
+ }
1091
+ return result;
1092
+ }
1093
+
1094
+ // src/verification/analysis/dbm.ts
1095
+ var EPSILON = 1e-9;
1096
+ var DBM = class _DBM {
1097
+ bounds;
1098
+ dim;
1099
+ clockNames;
1100
+ _empty;
1101
+ constructor(bounds, dim, clockNames, empty) {
1102
+ this.bounds = bounds;
1103
+ this.dim = dim;
1104
+ this.clockNames = clockNames;
1105
+ this._empty = empty;
1106
+ }
1107
+ /** Creates an initial firing domain for enabled transitions. */
1108
+ static create(clockNames, lowerBounds, upperBounds) {
1109
+ const n = clockNames.length;
1110
+ const dim = n + 1;
1111
+ const bounds = makeMatrix(dim, Infinity);
1112
+ for (let i = 0; i < n; i++) {
1113
+ bounds[0 * dim + (i + 1)] = -lowerBounds[i];
1114
+ bounds[(i + 1) * dim + 0] = upperBounds[i];
1115
+ }
1116
+ return new _DBM(bounds, dim, clockNames, false).canonicalize();
1117
+ }
1118
+ /** Creates an empty (unsatisfiable) zone. */
1119
+ static empty(clockNames) {
1120
+ const b = new Float64Array(1);
1121
+ b[0] = 0;
1122
+ return new _DBM(b, 1, clockNames, true);
1123
+ }
1124
+ isEmpty() {
1125
+ return this._empty;
1126
+ }
1127
+ clockCount() {
1128
+ return this.clockNames.length;
1129
+ }
1130
+ get(i, j) {
1131
+ return this.bounds[i * this.dim + j];
1132
+ }
1133
+ /** Gets the lower bound (earliest firing time) for clock i. */
1134
+ getLowerBound(clockIndex) {
1135
+ if (this._empty || clockIndex < 0 || clockIndex >= this.clockNames.length) return 0;
1136
+ const val = -this.get(0, clockIndex + 1);
1137
+ return val === 0 ? 0 : val;
1138
+ }
1139
+ /** Gets the upper bound (latest firing time / deadline) for clock i. */
1140
+ getUpperBound(clockIndex) {
1141
+ if (this._empty || clockIndex < 0 || clockIndex >= this.clockNames.length) return Infinity;
1142
+ return this.get(clockIndex + 1, 0);
1143
+ }
1144
+ /** Checks if transition can fire (lower bound <= 0 after time passage). */
1145
+ canFire(clockIndex) {
1146
+ return !this._empty && this.getLowerBound(clockIndex) <= EPSILON;
1147
+ }
1148
+ /**
1149
+ * Computes the successor firing domain after firing transition t_f.
1150
+ * Implements the 5-step Berthomieu-Diaz successor formula.
1151
+ */
1152
+ fireTransition(firedClock, newClockNames, newLowerBounds, newUpperBounds, persistentClocks) {
1153
+ if (this._empty) return this;
1154
+ const n = this.clockNames.length;
1155
+ if (firedClock < 0 || firedClock >= n) {
1156
+ throw new Error(`Invalid fired clock index: ${firedClock}`);
1157
+ }
1158
+ const constrained = new Float64Array(this.bounds);
1159
+ const dim = this.dim;
1160
+ const f = firedClock + 1;
1161
+ for (let i = 0; i < n; i++) {
1162
+ if (i !== firedClock) {
1163
+ const idx = i + 1;
1164
+ const pos = f * dim + idx;
1165
+ constrained[pos] = Math.min(constrained[pos], 0);
1166
+ }
1167
+ }
1168
+ if (!canonicalizeInPlace(constrained, dim)) {
1169
+ return _DBM.empty([]);
1170
+ }
1171
+ const newN = persistentClocks.length + newClockNames.length;
1172
+ const newDim = newN + 1;
1173
+ const newBounds = makeMatrix(newDim, Infinity);
1174
+ for (let pi = 0; pi < persistentClocks.length; pi++) {
1175
+ const oldIdx = persistentClocks[pi] + 1;
1176
+ const newIdx = pi + 1;
1177
+ const upper = constrained[oldIdx * dim + f];
1178
+ const lower = Math.max(0, -constrained[f * dim + oldIdx]);
1179
+ newBounds[0 * newDim + newIdx] = -lower;
1180
+ newBounds[newIdx * newDim + 0] = upper;
1181
+ for (let pj = 0; pj < persistentClocks.length; pj++) {
1182
+ const oldJ = persistentClocks[pj] + 1;
1183
+ const newJ = pj + 1;
1184
+ newBounds[newIdx * newDim + newJ] = constrained[oldIdx * dim + oldJ];
1185
+ }
1186
+ }
1187
+ const offset = persistentClocks.length;
1188
+ for (let k = 0; k < newClockNames.length; k++) {
1189
+ const idx = offset + k + 1;
1190
+ newBounds[0 * newDim + idx] = -newLowerBounds[k];
1191
+ newBounds[idx * newDim + 0] = newUpperBounds[k];
1192
+ }
1193
+ const allNames = [];
1194
+ for (const idx of persistentClocks) {
1195
+ allNames.push(this.clockNames[idx]);
1196
+ }
1197
+ allNames.push(...newClockNames);
1198
+ return new _DBM(newBounds, newDim, allNames, false).canonicalize();
1199
+ }
1200
+ /** Lets time pass: set all lower bounds to 0. */
1201
+ letTimePass() {
1202
+ if (this._empty) return this;
1203
+ const newBounds = new Float64Array(this.bounds);
1204
+ for (let i = 1; i < this.dim; i++) {
1205
+ newBounds[0 * this.dim + i] = 0;
1206
+ }
1207
+ return new _DBM(newBounds, this.dim, this.clockNames, false).canonicalize();
1208
+ }
1209
+ canonicalize() {
1210
+ if (this._empty) return this;
1211
+ const canon = new Float64Array(this.bounds);
1212
+ if (!canonicalizeInPlace(canon, this.dim)) {
1213
+ return _DBM.empty(this.clockNames);
1214
+ }
1215
+ return new _DBM(canon, this.dim, this.clockNames, false);
1216
+ }
1217
+ equals(other) {
1218
+ if (this === other) return true;
1219
+ if (this._empty && other._empty) return true;
1220
+ if (this._empty || other._empty) return false;
1221
+ if (this.clockNames.length !== other.clockNames.length) return false;
1222
+ for (let i = 0; i < this.clockNames.length; i++) {
1223
+ if (this.clockNames[i] !== other.clockNames[i]) return false;
1224
+ }
1225
+ if (this.bounds.length !== other.bounds.length) return false;
1226
+ for (let i = 0; i < this.bounds.length; i++) {
1227
+ if (Math.abs(this.bounds[i] - other.bounds[i]) > EPSILON) return false;
1228
+ }
1229
+ return true;
1230
+ }
1231
+ toString() {
1232
+ if (this._empty) return "DBM[empty]";
1233
+ const parts = [];
1234
+ for (let i = 0; i < this.clockNames.length; i++) {
1235
+ const lo = formatBound(this.getLowerBound(i));
1236
+ const hi = formatBound(this.getUpperBound(i));
1237
+ parts.push(`${this.clockNames[i]}:[${lo},${hi}]`);
1238
+ }
1239
+ return `DBM{${parts.join(", ")}}`;
1240
+ }
1241
+ };
1242
+ function makeMatrix(dim, fill) {
1243
+ const m = new Float64Array(dim * dim).fill(fill);
1244
+ for (let i = 0; i < dim; i++) {
1245
+ m[i * dim + i] = 0;
1246
+ }
1247
+ return m;
1248
+ }
1249
+ function canonicalizeInPlace(dbm, dim) {
1250
+ for (let k = 0; k < dim; k++) {
1251
+ for (let i = 0; i < dim; i++) {
1252
+ for (let j = 0; j < dim; j++) {
1253
+ const ik = dbm[i * dim + k];
1254
+ const kj = dbm[k * dim + j];
1255
+ if (ik < Infinity && kj < Infinity) {
1256
+ const via = ik + kj;
1257
+ if (via < dbm[i * dim + j]) {
1258
+ dbm[i * dim + j] = via;
1259
+ }
1260
+ }
1261
+ }
1262
+ }
1263
+ }
1264
+ for (let i = 0; i < dim; i++) {
1265
+ if (dbm[i * dim + i] < -EPSILON) return false;
1266
+ }
1267
+ return true;
1268
+ }
1269
+ function formatBound(b) {
1270
+ if (b >= Infinity / 2) return "\u221E";
1271
+ if (b === Math.trunc(b)) return String(b);
1272
+ return b.toFixed(3);
1273
+ }
1274
+
1275
+ // src/verification/analysis/state-class.ts
1276
+ var StateClass = class {
1277
+ marking;
1278
+ firingDomain;
1279
+ enabledTransitions;
1280
+ constructor(marking, firingDomain, enabledTransitions) {
1281
+ this.marking = marking;
1282
+ this.firingDomain = firingDomain;
1283
+ this.enabledTransitions = [...enabledTransitions];
1284
+ }
1285
+ isEmpty() {
1286
+ return this.firingDomain.isEmpty();
1287
+ }
1288
+ canFire(transition) {
1289
+ const idx = this.enabledTransitions.indexOf(transition);
1290
+ if (idx < 0) return false;
1291
+ return this.firingDomain.getUpperBound(idx) >= 0;
1292
+ }
1293
+ transitionIndex(transition) {
1294
+ return this.enabledTransitions.indexOf(transition);
1295
+ }
1296
+ equals(other) {
1297
+ if (this === other) return true;
1298
+ return this.marking.toString() === other.marking.toString() && this.firingDomain.equals(other.firingDomain);
1299
+ }
1300
+ toString() {
1301
+ return `StateClass{${this.marking}, ${this.firingDomain}}`;
1302
+ }
1303
+ };
1304
+
1305
+ // src/verification/analysis/state-class-graph.ts
1306
+ var StateClassGraph = class _StateClassGraph {
1307
+ net;
1308
+ initialClass;
1309
+ _stateClasses;
1310
+ _transitions;
1311
+ _successors;
1312
+ _predecessors;
1313
+ _complete;
1314
+ constructor(net, initialClass, stateClasses, transitions, complete) {
1315
+ this.net = net;
1316
+ this.initialClass = initialClass;
1317
+ this._stateClasses = stateClasses;
1318
+ this._transitions = transitions;
1319
+ this._complete = complete;
1320
+ this._successors = /* @__PURE__ */ new Map();
1321
+ this._predecessors = /* @__PURE__ */ new Map();
1322
+ for (const sc of stateClasses) {
1323
+ this._successors.set(sc, /* @__PURE__ */ new Set());
1324
+ this._predecessors.set(sc, /* @__PURE__ */ new Set());
1325
+ }
1326
+ for (const [from, tMap] of transitions) {
1327
+ for (const edges of tMap.values()) {
1328
+ for (const edge of edges) {
1329
+ this._successors.get(from).add(edge.target);
1330
+ this._predecessors.get(edge.target).add(from);
1331
+ }
1332
+ }
1333
+ }
1334
+ }
1335
+ /** Builds the state class graph for a Time Petri Net. */
1336
+ static build(net, initialMarking, maxClasses, environmentPlaces, environmentMode) {
1337
+ const envMode = environmentMode ?? ignore();
1338
+ const envPlaces = /* @__PURE__ */ new Set();
1339
+ if (environmentPlaces) {
1340
+ for (const ep of environmentPlaces) {
1341
+ envPlaces.add(ep.place);
1342
+ }
1343
+ }
1344
+ const initialClass = initialStateClass(net, initialMarking, envPlaces, envMode);
1345
+ const stateClasses = [initialClass];
1346
+ const stateClassSet = /* @__PURE__ */ new Set([classKey(initialClass)]);
1347
+ const classMap = /* @__PURE__ */ new Map([[classKey(initialClass), initialClass]]);
1348
+ const transitionMap = /* @__PURE__ */ new Map();
1349
+ transitionMap.set(initialClass, /* @__PURE__ */ new Map());
1350
+ const queue = [initialClass];
1351
+ let complete = true;
1352
+ while (queue.length > 0) {
1353
+ if (stateClasses.length >= maxClasses) {
1354
+ complete = false;
1355
+ break;
1356
+ }
1357
+ const current = queue.shift();
1358
+ for (const transition of current.enabledTransitions) {
1359
+ const virtualTransitions = expandTransition(transition);
1360
+ for (const vt of virtualTransitions) {
1361
+ const successor = computeSuccessor(net, current, vt, envPlaces, envMode);
1362
+ if (successor === null || successor.isEmpty()) continue;
1363
+ const tEdges = transitionMap.get(current);
1364
+ if (!tEdges.has(transition)) tEdges.set(transition, []);
1365
+ tEdges.get(transition).push({ branchIndex: vt.branchIndex, target: successor });
1366
+ const key = classKey(successor);
1367
+ if (!stateClassSet.has(key)) {
1368
+ stateClassSet.add(key);
1369
+ classMap.set(key, successor);
1370
+ stateClasses.push(successor);
1371
+ transitionMap.set(successor, /* @__PURE__ */ new Map());
1372
+ queue.push(successor);
1373
+ } else {
1374
+ const canonical = classMap.get(key);
1375
+ if (canonical !== successor) {
1376
+ const edges = tEdges.get(transition);
1377
+ edges[edges.length - 1] = { branchIndex: vt.branchIndex, target: canonical };
1378
+ }
1379
+ }
1380
+ }
1381
+ }
1382
+ }
1383
+ return new _StateClassGraph(net, initialClass, stateClasses, transitionMap, complete);
1384
+ }
1385
+ stateClasses() {
1386
+ return this._stateClasses;
1387
+ }
1388
+ size() {
1389
+ return this._stateClasses.length;
1390
+ }
1391
+ isComplete() {
1392
+ return this._complete;
1393
+ }
1394
+ successors(sc) {
1395
+ return this._successors.get(sc) ?? /* @__PURE__ */ new Set();
1396
+ }
1397
+ predecessors(sc) {
1398
+ return this._predecessors.get(sc) ?? /* @__PURE__ */ new Set();
1399
+ }
1400
+ /** Returns all outgoing transitions with their branch edges. */
1401
+ outgoingBranchEdges(sc) {
1402
+ return this._transitions.get(sc) ?? /* @__PURE__ */ new Map();
1403
+ }
1404
+ /** Returns the branch edges for a specific transition from a state class. */
1405
+ branchEdges(sc, transition) {
1406
+ const map = this._transitions.get(sc);
1407
+ if (!map) return [];
1408
+ return map.get(transition) ?? [];
1409
+ }
1410
+ /** Returns all transitions that are enabled from a state class. */
1411
+ enabledTransitions(sc) {
1412
+ const map = this._transitions.get(sc);
1413
+ if (!map) return /* @__PURE__ */ new Set();
1414
+ return new Set(map.keys());
1415
+ }
1416
+ /** Finds all state classes with a given marking. */
1417
+ classesWithMarking(marking) {
1418
+ const key = marking.toString();
1419
+ return this._stateClasses.filter((sc) => sc.marking.toString() === key);
1420
+ }
1421
+ /** Checks if a marking is reachable. */
1422
+ isReachable(marking) {
1423
+ const key = marking.toString();
1424
+ return this._stateClasses.some((sc) => sc.marking.toString() === key);
1425
+ }
1426
+ /** Gets all reachable markings. */
1427
+ reachableMarkings() {
1428
+ const markings = /* @__PURE__ */ new Set();
1429
+ for (const sc of this._stateClasses) {
1430
+ markings.add(sc.marking.toString());
1431
+ }
1432
+ return markings;
1433
+ }
1434
+ /** Counts edges in the graph (each branch edge counts separately). */
1435
+ edgeCount() {
1436
+ let count = 0;
1437
+ for (const map of this._transitions.values()) {
1438
+ for (const edges of map.values()) {
1439
+ count += edges.length;
1440
+ }
1441
+ }
1442
+ return count;
1443
+ }
1444
+ toString() {
1445
+ return `StateClassGraph[classes=${this.size()}, edges=${this.edgeCount()}, complete=${this._complete}]`;
1446
+ }
1447
+ };
1448
+ function classKey(sc) {
1449
+ return `${sc.marking.toString()}|${sc.firingDomain.toString()}`;
1450
+ }
1451
+ function initialStateClass(net, initialMarking, envPlaces, envMode) {
1452
+ const enabledTransitions = findEnabledTransitions(net, initialMarking, envPlaces, envMode);
1453
+ const clockNames = enabledTransitions.map((t) => t.name);
1454
+ const lowerBounds = enabledTransitions.map((t) => earliest(t.timing) / 1e3);
1455
+ const upperBounds = enabledTransitions.map((t) => latest(t.timing) / 1e3);
1456
+ let initialDBM = DBM.create(clockNames, lowerBounds, upperBounds);
1457
+ initialDBM = initialDBM.letTimePass();
1458
+ return new StateClass(initialMarking, initialDBM, enabledTransitions);
1459
+ }
1460
+ function expandTransition(t) {
1461
+ let branches;
1462
+ if (t.outputSpec !== null) {
1463
+ branches = enumerateBranches(t.outputSpec);
1464
+ } else {
1465
+ branches = [/* @__PURE__ */ new Set()];
1466
+ }
1467
+ return branches.map((outputPlaces, i) => ({
1468
+ transition: t,
1469
+ branchIndex: i,
1470
+ outputPlaces
1471
+ }));
1472
+ }
1473
+ function computeSuccessor(net, current, fired, environmentPlaces, environmentMode) {
1474
+ const transition = fired.transition;
1475
+ const newMarking = fireTransition(current.marking, transition, fired.outputPlaces, environmentPlaces, environmentMode);
1476
+ const newEnabledAll = findEnabledTransitions(net, newMarking, environmentPlaces, environmentMode);
1477
+ const persistent = [];
1478
+ const persistentIndices = [];
1479
+ for (let i = 0; i < current.enabledTransitions.length; i++) {
1480
+ const t = current.enabledTransitions[i];
1481
+ if (t !== transition && newEnabledAll.includes(t)) {
1482
+ persistent.push(t);
1483
+ persistentIndices.push(i);
1484
+ }
1485
+ }
1486
+ const newlyEnabled = [];
1487
+ for (const t of newEnabledAll) {
1488
+ if (!persistent.includes(t)) {
1489
+ newlyEnabled.push(t);
1490
+ }
1491
+ }
1492
+ const firedIdx = current.transitionIndex(transition);
1493
+ const newClockNames = newlyEnabled.map((t) => t.name);
1494
+ const newLowerBounds = newlyEnabled.map((t) => earliest(t.timing) / 1e3);
1495
+ const newUpperBounds = newlyEnabled.map((t) => latest(t.timing) / 1e3);
1496
+ let newDBM = current.firingDomain.fireTransition(
1497
+ firedIdx,
1498
+ newClockNames,
1499
+ newLowerBounds,
1500
+ newUpperBounds,
1501
+ persistentIndices
1502
+ );
1503
+ newDBM = newDBM.letTimePass();
1504
+ const allEnabled = [...persistent, ...newlyEnabled];
1505
+ return new StateClass(newMarking, newDBM, allEnabled);
1506
+ }
1507
+ function findEnabledTransitions(net, marking, environmentPlaces, environmentMode) {
1508
+ const enabled = [];
1509
+ for (const transition of net.transitions) {
1510
+ if (isEnabled(transition, marking, environmentPlaces, environmentMode)) {
1511
+ enabled.push(transition);
1512
+ }
1513
+ }
1514
+ return enabled;
1515
+ }
1516
+ function isEnabled(transition, marking, environmentPlaces, environmentMode) {
1517
+ for (const spec of transition.inputSpecs) {
1518
+ const required = inputRequiredCount(spec);
1519
+ if (!checkPlaceEnabled(spec.place, required, marking, environmentPlaces, environmentMode)) {
1520
+ return false;
1521
+ }
1522
+ }
1523
+ for (const arc of transition.reads) {
1524
+ if (!checkPlaceEnabled(arc.place, 1, marking, environmentPlaces, environmentMode)) {
1525
+ return false;
1526
+ }
1527
+ }
1528
+ for (const arc of transition.inhibitors) {
1529
+ if (marking.hasTokens(arc.place)) {
1530
+ return false;
1531
+ }
1532
+ }
1533
+ return true;
1534
+ }
1535
+ function inputRequiredCount(spec) {
1536
+ switch (spec.type) {
1537
+ case "one":
1538
+ return 1;
1539
+ case "exactly":
1540
+ return spec.count;
1541
+ case "all":
1542
+ return 1;
1543
+ case "at-least":
1544
+ return spec.minimum;
1545
+ }
1546
+ }
1547
+ function inputConsumeCount(spec) {
1548
+ switch (spec.type) {
1549
+ case "one":
1550
+ return 1;
1551
+ case "exactly":
1552
+ return spec.count;
1553
+ case "all":
1554
+ return 1;
1555
+ // Analysis: consume minimum (1 token)
1556
+ case "at-least":
1557
+ return spec.minimum;
1558
+ }
1559
+ }
1560
+ function checkPlaceEnabled(place, required, marking, environmentPlaces, environmentMode) {
1561
+ if (!environmentPlaces.has(place)) {
1562
+ return marking.tokens(place) >= required;
1563
+ }
1564
+ switch (environmentMode.type) {
1565
+ case "always-available":
1566
+ return true;
1567
+ case "bounded":
1568
+ return required <= environmentMode.maxTokens;
1569
+ case "ignore":
1570
+ return marking.tokens(place) >= required;
1571
+ }
1572
+ }
1573
+ function fireTransition(marking, transition, outputPlaces, environmentPlaces, environmentMode) {
1574
+ const builder = MarkingState.builder().copyFrom(marking);
1575
+ for (const spec of transition.inputSpecs) {
1576
+ const toConsume = inputConsumeCount(spec);
1577
+ consumeFromPlace(builder, spec.place, toConsume, environmentPlaces, environmentMode);
1578
+ }
1579
+ for (const arc of transition.resets) {
1580
+ const current = marking.tokens(arc.place);
1581
+ if (current > 0) {
1582
+ builder.removeTokens(arc.place, current);
1583
+ }
1584
+ }
1585
+ for (const place of outputPlaces) {
1586
+ builder.addTokens(place, 1);
1587
+ }
1588
+ return builder.build();
1589
+ }
1590
+ function consumeFromPlace(builder, place, count, environmentPlaces, environmentMode) {
1591
+ if (!environmentPlaces.has(place)) {
1592
+ builder.removeTokens(place, count);
1593
+ return;
1594
+ }
1595
+ if (environmentMode.type === "ignore") {
1596
+ builder.removeTokens(place, count);
1597
+ }
1598
+ }
1599
+
1600
+ // src/verification/z3/counterexample-decoder.ts
1601
+ function decode(ctx, answer, flatNet) {
1602
+ const trace = [];
1603
+ const transitions = [];
1604
+ if (answer == null) {
1605
+ return { trace, transitions };
1606
+ }
1607
+ try {
1608
+ extractTrace(ctx, answer, flatNet, trace, transitions);
1609
+ } catch {
1610
+ }
1611
+ return { trace, transitions };
1612
+ }
1613
+ function extractTrace(ctx, expr, flatNet, trace, transitions) {
1614
+ if (expr == null) return;
1615
+ if (!ctx.isApp(expr)) return;
1616
+ let name;
1617
+ try {
1618
+ const decl = expr.decl();
1619
+ name = String(decl.name());
1620
+ } catch {
1621
+ return;
1622
+ }
1623
+ const P = flatNet.places.length;
1624
+ if (name === "Reachable") {
1625
+ const numArgs = expr.numArgs();
1626
+ if (numArgs === P) {
1627
+ const marking = extractMarking(ctx, expr, flatNet);
1628
+ if (marking != null) {
1629
+ trace.push(marking);
1630
+ }
1631
+ }
1632
+ }
1633
+ try {
1634
+ const numArgs = expr.numArgs();
1635
+ for (let i = 0; i < numArgs; i++) {
1636
+ const child = expr.arg(i);
1637
+ extractTrace(ctx, child, flatNet, trace, transitions);
1638
+ }
1639
+ } catch {
1640
+ }
1641
+ if (name.startsWith("t_")) {
1642
+ transitions.push(name.substring(2));
1643
+ }
1644
+ }
1645
+ function extractMarking(ctx, reachableApp, flatNet) {
1646
+ const P = flatNet.places.length;
1647
+ if (reachableApp.numArgs() !== P) return null;
1648
+ const builder = MarkingState.builder();
1649
+ for (let i = 0; i < P; i++) {
1650
+ const arg = reachableApp.arg(i);
1651
+ if (ctx.isIntVal(arg)) {
1652
+ const tokens = Number(arg.value());
1653
+ if (tokens > 0) {
1654
+ builder.tokens(flatNet.places[i], tokens);
1655
+ }
1656
+ } else {
1657
+ return null;
1658
+ }
1659
+ }
1660
+ return builder.build();
1661
+ }
1662
+
1663
+ // src/verification/z3/name-coloured-encoder.ts
1664
+ function buildColouredPlan(net, flat, initial, budgetNames) {
1665
+ const P = flat.places.length;
1666
+ if (flat.transitions.length !== [...net.transitions].length) {
1667
+ return null;
1668
+ }
1669
+ const isColoured = new Array(P).fill(false);
1670
+ for (const ft of flat.transitions) {
1671
+ const ms = ft.source.matchSpec;
1672
+ if (ms) {
1673
+ for (const key of ms.keys) {
1674
+ const pid = flat.placeIndex.get(key.place.name);
1675
+ if (pid == null) return null;
1676
+ isColoured[pid] = true;
1677
+ }
1678
+ }
1679
+ }
1680
+ const coloured = [];
1681
+ for (let i = 0; i < P; i++) if (isColoured[i]) coloured.push(i);
1682
+ if (coloured.length === 0) return null;
1683
+ for (const pid of coloured) {
1684
+ if (initial.tokens(flat.places[pid]) !== 0) return null;
1685
+ }
1686
+ const budgetIdx = /* @__PURE__ */ new Set();
1687
+ for (const n of budgetNames) {
1688
+ const i = flat.placeIndex.get(n);
1689
+ if (i != null) budgetIdx.add(i);
1690
+ }
1691
+ if (budgetIdx.size === 0) return null;
1692
+ let k = 0;
1693
+ for (const b of budgetIdx) k += initial.tokens(flat.places[b]);
1694
+ if (k === 0) return null;
1695
+ for (const ft of flat.transitions) {
1696
+ const touches = ft.inhibitorPlaces.some((i) => isColoured[i]) || ft.readPlaces.some((i) => isColoured[i]) || ft.resetPlaces.some((i) => isColoured[i]) || ft.consumeAll.some((ca, i) => ca && isColoured[i]);
1697
+ if (touches) return null;
1698
+ }
1699
+ const classes = [];
1700
+ for (const ft of flat.transitions) {
1701
+ const colouredIn = coloured.filter((pid) => ft.preVector[pid] > 0);
1702
+ const colouredOut = coloured.filter((pid) => ft.postVector[pid] > 0);
1703
+ const ms = ft.source.matchSpec;
1704
+ if (ms) {
1705
+ if (colouredOut.length !== 0 || colouredIn.length === 0) return null;
1706
+ if (colouredIn.some((pid) => ft.preVector[pid] !== 1)) return null;
1707
+ classes.push({ kind: "join", colouredIn });
1708
+ } else if (colouredOut.length !== 0) {
1709
+ if (colouredIn.length !== 0) return null;
1710
+ if (colouredOut.some((o) => ft.postVector[o] !== 1)) return null;
1711
+ let budgetConsumed = 0;
1712
+ for (const b of budgetIdx) budgetConsumed += ft.preVector[b];
1713
+ if (budgetConsumed < 1) return null;
1714
+ classes.push({ kind: "mint", colouredOut });
1715
+ } else {
1716
+ if (colouredIn.length !== 0) return null;
1717
+ classes.push({ kind: "untouched" });
1718
+ }
1719
+ }
1720
+ let minMintCost = null;
1721
+ let maxJoinRefund = 0;
1722
+ for (let ti = 0; ti < classes.length; ti++) {
1723
+ const cls = classes[ti];
1724
+ const ft = flat.transitions[ti];
1725
+ for (const b of budgetIdx) {
1726
+ if (ft.preVector[b] > 0 && cls.kind !== "mint") return null;
1727
+ if (ft.postVector[b] > 0 && cls.kind !== "join") return null;
1728
+ }
1729
+ if (cls.kind === "mint") {
1730
+ let cost = 0;
1731
+ for (const b of budgetIdx) cost += ft.preVector[b];
1732
+ minMintCost = minMintCost === null ? cost : Math.min(minMintCost, cost);
1733
+ } else if (cls.kind === "join") {
1734
+ let refund = 0;
1735
+ for (const b of budgetIdx) refund += ft.postVector[b];
1736
+ maxJoinRefund = Math.max(maxJoinRefund, refund);
1737
+ }
1738
+ }
1739
+ if (minMintCost === null || maxJoinRefund > minMintCost) return null;
1740
+ return { coloured, isColoured, k, classes };
1741
+ }
1742
+ function buildLayout(ctx, plan, P) {
1743
+ const colUnc = new Array(P).fill(-1);
1744
+ const colCol = Array.from({ length: P }, () => []);
1745
+ let nCols = 0;
1746
+ for (let i = 0; i < P; i++) {
1747
+ if (plan.isColoured[i]) {
1748
+ const idxs = [];
1749
+ for (let c = 0; c < plan.k; c++) idxs.push(nCols++);
1750
+ colCol[i] = idxs;
1751
+ } else {
1752
+ colUnc[i] = nCols++;
1753
+ }
1754
+ }
1755
+ const cur = [];
1756
+ const nxt = [];
1757
+ for (let col = 0; col < nCols; col++) {
1758
+ cur.push(ctx.Int.const(`c${col}`));
1759
+ nxt.push(ctx.Int.const(`cp${col}`));
1760
+ }
1761
+ return { colUnc, colCol, nCols, cur, nxt };
1762
+ }
1763
+ function encodeColoured(ctx, fp, plan, flat, initial, property, invariants) {
1764
+ const P = flat.places.length;
1765
+ const k = plan.k;
1766
+ const lay = buildLayout(ctx, plan, P);
1767
+ const intSort = ctx.Int.sort();
1768
+ const boolSort = ctx.Bool.sort();
1769
+ const markingSorts = new Array(lay.nCols).fill(intSort);
1770
+ const reachable = ctx.Function.declare("Reachable", ...markingSorts, boolSort);
1771
+ fp.registerRelation(reachable);
1772
+ const error = ctx.Function.declare("Error", boolSort);
1773
+ fp.registerRelation(error);
1774
+ const initArgs = new Array(lay.nCols);
1775
+ for (let i = 0; i < P; i++) {
1776
+ if (plan.isColoured[i]) {
1777
+ for (let c = 0; c < k; c++) initArgs[lay.colCol[i][c]] = ctx.Int.val(0);
1778
+ } else {
1779
+ initArgs[lay.colUnc[i]] = ctx.Int.val(initial.tokens(flat.places[i]));
1780
+ }
1781
+ }
1782
+ fp.addRule(reachable.call(...initArgs), "init");
1783
+ for (let ti = 0; ti < plan.classes.length; ti++) {
1784
+ const cls = plan.classes[ti];
1785
+ const ft = flat.transitions[ti];
1786
+ if (cls.kind === "untouched") {
1787
+ addRule(
1788
+ ctx,
1789
+ fp,
1790
+ reachable,
1791
+ lay,
1792
+ plan,
1793
+ invariants,
1794
+ `${ft.name}_u`,
1795
+ (enab, upd) => uncolouredIncidence(ctx, lay, plan, ft, enab, upd)
1796
+ );
1797
+ } else if (cls.kind === "mint") {
1798
+ const colouredOut = cls.colouredOut;
1799
+ for (let c = 0; c < k; c++) {
1800
+ const cc = c;
1801
+ addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_mint_${cc}`, (enab, upd) => {
1802
+ uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
1803
+ for (const q of plan.coloured) enab.push(lay.cur[lay.colCol[q][cc]].eq(0));
1804
+ for (const o of colouredOut) {
1805
+ const col = lay.colCol[o][cc];
1806
+ upd.set(col, lay.cur[col].add(1));
1807
+ }
1808
+ });
1809
+ }
1810
+ } else {
1811
+ const colouredIn = cls.colouredIn;
1812
+ for (let c = 0; c < k; c++) {
1813
+ const cc = c;
1814
+ addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_join_${cc}`, (enab, upd) => {
1815
+ uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
1816
+ for (const ip of colouredIn) {
1817
+ const col = lay.colCol[ip][cc];
1818
+ enab.push(lay.cur[col].ge(1));
1819
+ upd.set(col, lay.cur[col].add(-1));
1820
+ }
1821
+ });
1822
+ }
1823
+ }
1824
+ }
1825
+ addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property);
1826
+ return {
1827
+ errorExpr: error.call(),
1828
+ reachableDecl: reachable
1829
+ };
1830
+ }
1831
+ function addRule(ctx, fp, reachable, lay, plan, invariants, ruleName, fill) {
1832
+ const enab = [];
1833
+ const upd = /* @__PURE__ */ new Map();
1834
+ fill(enab, upd);
1835
+ const conds = [reachable.call(...lay.cur), ...enab];
1836
+ for (let col = 0; col < lay.nCols; col++) {
1837
+ const expr = upd.get(col);
1838
+ if (expr !== void 0) {
1839
+ conds.push(lay.nxt[col].eq(expr), lay.nxt[col].ge(0));
1840
+ } else {
1841
+ conds.push(lay.nxt[col].eq(lay.cur[col]));
1842
+ }
1843
+ }
1844
+ for (const inv of invariants) {
1845
+ const eq = liftedInvariant(ctx, inv, plan, lay, lay.nxt);
1846
+ if (eq) conds.push(eq);
1847
+ }
1848
+ const body = ctx.And(...conds);
1849
+ const head = reachable.call(...lay.nxt);
1850
+ const qRule = ctx.ForAll([...lay.cur, ...lay.nxt], ctx.Implies(body, head));
1851
+ fp.addRule(qRule, ruleName);
1852
+ }
1853
+ function uncolouredIncidence(ctx, lay, plan, ft, enab, upd) {
1854
+ const P = ft.preVector.length;
1855
+ for (let i = 0; i < P; i++) {
1856
+ if (plan.isColoured[i]) continue;
1857
+ const col = lay.colUnc[i];
1858
+ const pre = ft.preVector[i];
1859
+ if (pre > 0) enab.push(lay.cur[col].ge(pre));
1860
+ if (ft.resetPlaces.includes(i) || ft.consumeAll[i]) {
1861
+ upd.set(col, ctx.Int.val(ft.postVector[i]));
1862
+ } else {
1863
+ const delta = ft.postVector[i] - ft.preVector[i];
1864
+ if (delta !== 0) upd.set(col, lay.cur[col].add(delta));
1865
+ }
1866
+ }
1867
+ for (const pid of ft.inhibitorPlaces) enab.push(lay.cur[lay.colUnc[pid]].eq(0));
1868
+ for (const pid of ft.readPlaces) enab.push(lay.cur[lay.colUnc[pid]].ge(1));
1869
+ }
1870
+ function aggregate(plan, lay, place, vars) {
1871
+ if (plan.isColoured[place]) {
1872
+ const cols = lay.colCol[place];
1873
+ let sum = vars[cols[0]];
1874
+ for (let c = 1; c < cols.length; c++) sum = sum.add(vars[cols[c]]);
1875
+ return sum;
1876
+ }
1877
+ return vars[lay.colUnc[place]];
1878
+ }
1879
+ function liftedInvariant(ctx, inv, plan, lay, vars) {
1880
+ if (inv.support.size === 0) return null;
1881
+ let sum = ctx.Int.val(0);
1882
+ for (const i of inv.support) {
1883
+ const agg = aggregate(plan, lay, i, vars);
1884
+ const w = inv.weights[i];
1885
+ sum = sum.add(w === 1 ? agg : agg.mul(w));
1886
+ }
1887
+ return sum.eq(inv.constant);
1888
+ }
1889
+ function addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property) {
1890
+ const reachBody = reachable.call(...lay.cur);
1891
+ const violation = encodeViolation(ctx, plan, lay, flat, property, lay.cur);
1892
+ const body = ctx.And(reachBody, violation);
1893
+ const head = error.call();
1894
+ const qRule = ctx.ForAll([...lay.cur], ctx.Implies(body, head));
1895
+ fp.addRule(qRule, "error");
1896
+ }
1897
+ function encodeViolation(ctx, plan, lay, flat, property, cur) {
1898
+ switch (property.type) {
1899
+ case "place-bound":
1900
+ case "branch-place-bound": {
1901
+ const idx = flatNetIndexOf(flat, property.place);
1902
+ if (idx < 0) return ctx.Bool.val(false);
1903
+ return aggregate(plan, lay, idx, cur).gt(property.bound);
1904
+ }
1905
+ case "mutual-exclusion": {
1906
+ const i1 = flatNetIndexOf(flat, property.p1);
1907
+ const i2 = flatNetIndexOf(flat, property.p2);
1908
+ if (i1 < 0 || i2 < 0) return ctx.Bool.val(false);
1909
+ return ctx.And(aggregate(plan, lay, i1, cur).ge(1), aggregate(plan, lay, i2, cur).ge(1));
1910
+ }
1911
+ case "unreachable": {
1912
+ const conds = [];
1913
+ for (const place of property.places) {
1914
+ const idx = flatNetIndexOf(flat, place);
1915
+ if (idx >= 0) conds.push(aggregate(plan, lay, idx, cur).ge(1));
1916
+ }
1917
+ if (conds.length === 0) return ctx.Bool.val(false);
1918
+ return conds.length === 1 ? conds[0] : ctx.And(...conds);
1919
+ }
1920
+ // Quiescence properties are never routed here.
1921
+ case "deadlock-free":
1922
+ case "joined-or-dead-lettered":
1923
+ return ctx.Bool.val(false);
1924
+ }
1925
+ }
1926
+
1927
+ // src/verification/analysis/name-fragment.ts
1928
+ function classify(net) {
1929
+ const coloured = /* @__PURE__ */ new Set();
1930
+ let anyMatch = false;
1931
+ for (const t of net.transitions) {
1932
+ if (t.matchSpec !== null) {
1933
+ anyMatch = true;
1934
+ for (const key of t.matchSpec.keys) coloured.add(key.place.name);
1935
+ }
1936
+ }
1937
+ if (!anyMatch || coloured.size === 0) return null;
1938
+ const roles = /* @__PURE__ */ new Map();
1939
+ for (const t of net.transitions) {
1940
+ const consumesColoured = t.inputSpecs.some((s) => coloured.has(s.place.name));
1941
+ let producesColoured = false;
1942
+ if (t.outputSpec !== null) {
1943
+ for (const branch of enumerateBranches(t.outputSpec)) {
1944
+ for (const p of branch) {
1945
+ if (coloured.has(p.name)) producesColoured = true;
1946
+ }
1947
+ }
1948
+ }
1949
+ let role;
1950
+ if (t.matchSpec !== null) {
1951
+ if (producesColoured) return null;
1952
+ const colouredIn = [];
1953
+ for (const key of t.matchSpec.keys) {
1954
+ const place = key.place.name;
1955
+ const required = fixedRequiredCount(t, place);
1956
+ if (required === null) return null;
1957
+ colouredIn.push([place, required]);
1958
+ }
1959
+ colouredIn.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
1960
+ role = { type: "join", colouredIn };
1961
+ } else if (producesColoured) {
1962
+ if (consumesColoured) return null;
1963
+ role = { type: "mint" };
1964
+ } else {
1965
+ if (consumesColoured) return null;
1966
+ role = { type: "ordinary" };
1967
+ }
1968
+ roles.set(t.name, role);
1969
+ }
1970
+ const colouredOrder = [...coloured].sort();
1971
+ return {
1972
+ colouredOrder,
1973
+ isColoured: (p) => coloured.has(p),
1974
+ role: (tn) => roles.get(tn) ?? { type: "ordinary" }
1975
+ };
1976
+ }
1977
+ function fixedRequiredCount(t, placeName) {
1978
+ for (const spec of t.inputSpecs) {
1979
+ if (spec.place.name === placeName) {
1980
+ switch (spec.type) {
1981
+ case "one":
1982
+ return 1;
1983
+ case "exactly":
1984
+ return spec.count;
1985
+ case "all":
1986
+ return null;
1987
+ case "at-least":
1988
+ return null;
1989
+ }
1990
+ }
1991
+ }
1992
+ return null;
1993
+ }
1994
+
1995
+ // src/verification/analysis/name-marking.ts
1996
+ var NameMarking = class _NameMarking {
1997
+ // place name -> (symbol -> count). Only coloured places appear; a place's
1998
+ // total here equals its count in the base MarkingState.
1999
+ perPlace;
2000
+ constructor(perPlace) {
2001
+ this.perPlace = perPlace ?? /* @__PURE__ */ new Map();
2002
+ }
2003
+ copy() {
2004
+ const p = /* @__PURE__ */ new Map();
2005
+ for (const [place, syms] of this.perPlace) {
2006
+ p.set(place, new Map(syms));
2007
+ }
2008
+ return new _NameMarking(p);
2009
+ }
2010
+ add(place, sym, count) {
2011
+ if (count === 0) return;
2012
+ let syms = this.perPlace.get(place);
2013
+ if (!syms) {
2014
+ syms = /* @__PURE__ */ new Map();
2015
+ this.perPlace.set(place, syms);
2016
+ }
2017
+ syms.set(sym, (syms.get(sym) ?? 0) + count);
2018
+ }
2019
+ /** Removes `count` of `sym` from `place`; returns false (unchanged) if fewer present. */
2020
+ remove(place, sym, count) {
2021
+ const syms = this.perPlace.get(place);
2022
+ if (!syms) return false;
2023
+ const have = syms.get(sym);
2024
+ if (have === void 0 || have < count) return false;
2025
+ const left = have - count;
2026
+ if (left === 0) {
2027
+ syms.delete(sym);
2028
+ if (syms.size === 0) this.perPlace.delete(place);
2029
+ } else {
2030
+ syms.set(sym, left);
2031
+ }
2032
+ return true;
2033
+ }
2034
+ countOf(place, sym) {
2035
+ return this.perPlace.get(place)?.get(sym) ?? 0;
2036
+ }
2037
+ symbolsIn(place) {
2038
+ const syms = this.perPlace.get(place);
2039
+ return syms ? [...syms.keys()] : [];
2040
+ }
2041
+ liveSymbols() {
2042
+ const all = /* @__PURE__ */ new Set();
2043
+ for (const syms of this.perPlace.values()) {
2044
+ for (const s of syms.keys()) all.add(s);
2045
+ }
2046
+ return [...all];
2047
+ }
2048
+ /**
2049
+ * Symmetry-canonical key over `colouredOrder` (the finiteness mechanism). Two
2050
+ * markings differing only by a permutation of symbols produce an identical key
2051
+ * (NU-001). Each symbol's signature is its count vector over `colouredOrder`;
2052
+ * symbols are ranked by (signature, raw id) and emitted per place as a
2053
+ * rank-multiset — a complete invariant of the symbol-permutation orbit.
2054
+ */
2055
+ canonicalKey(colouredOrder) {
2056
+ const signature = (s) => colouredOrder.map((p) => this.countOf(p, s));
2057
+ const ranked = this.liveSymbols().map((s) => ({ sig: signature(s), sym: s }));
2058
+ ranked.sort((a, b) => {
2059
+ const c = compareNumberArrays(a.sig, b.sig);
2060
+ return c !== 0 ? c : a.sym - b.sym;
2061
+ });
2062
+ const rankOf = /* @__PURE__ */ new Map();
2063
+ ranked.forEach((r, i) => rankOf.set(r.sym, i));
2064
+ const parts = colouredOrder.map((p) => {
2065
+ const syms = this.perPlace.get(p);
2066
+ const entries = [];
2067
+ if (syms) {
2068
+ for (const [s, c] of syms) entries.push([rankOf.get(s), c]);
2069
+ }
2070
+ entries.sort((a, b) => a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1]);
2071
+ const inner = entries.map(([r, c]) => `${r}x${c}`).join(",");
2072
+ return `${p}:{${inner}}`;
2073
+ });
2074
+ return parts.join("#");
2075
+ }
2076
+ };
2077
+ function compareNumberArrays(a, b) {
2078
+ const n = Math.min(a.length, b.length);
2079
+ for (let i = 0; i < n; i++) {
2080
+ if (a[i] !== b[i]) return a[i] - b[i];
2081
+ }
2082
+ return a.length - b.length;
2083
+ }
2084
+
2085
+ // src/verification/analysis/name-state-class.ts
2086
+ var NameStateClass = class {
2087
+ base;
2088
+ names;
2089
+ key;
2090
+ constructor(base, names, colouredOrder) {
2091
+ this.base = base;
2092
+ this.names = names;
2093
+ this.key = `${base.marking.toString()}|${base.firingDomain.toString()}||${names.canonicalKey(colouredOrder)}`;
2094
+ }
2095
+ };
2096
+
2097
+ // src/verification/analysis/name-state-class-graph.ts
2098
+ var NameStateClassGraph = class _NameStateClassGraph {
2099
+ classes = [];
2100
+ edges = [];
2101
+ _successors = [];
2102
+ _complete = true;
2103
+ isComplete() {
2104
+ return this._complete;
2105
+ }
2106
+ classCount() {
2107
+ return this.classes.length;
2108
+ }
2109
+ successorsOf(idx) {
2110
+ return this._successors[idx];
2111
+ }
2112
+ /** The base count-marking of class `idx` (for property queries). */
2113
+ markingOf(idx) {
2114
+ return this.classes[idx].base.marking;
2115
+ }
2116
+ static build(net, initialMarking, fragment, maxClasses, environmentPlaces, environmentMode) {
2117
+ const envMode = environmentMode ?? ignore();
2118
+ const envPlaces = /* @__PURE__ */ new Set();
2119
+ if (environmentPlaces) {
2120
+ for (const ep of environmentPlaces) envPlaces.add(ep.place);
2121
+ }
2122
+ const graph = new _NameStateClassGraph();
2123
+ const base0 = initialStateClass(net, initialMarking, envPlaces, envMode);
2124
+ const initial = new NameStateClass(base0, new NameMarking(), fragment.colouredOrder);
2125
+ const indexOf = /* @__PURE__ */ new Map();
2126
+ graph.pushClass(initial, indexOf);
2127
+ const sym = { next: 0 };
2128
+ const queue = [0];
2129
+ while (queue.length > 0) {
2130
+ if (graph.classes.length >= maxClasses) {
2131
+ graph._complete = false;
2132
+ break;
2133
+ }
2134
+ const curIdx = queue.shift();
2135
+ const current = graph.classes[curIdx];
2136
+ for (const transition of current.base.enabledTransitions) {
2137
+ const role = fragment.role(transition.name);
2138
+ for (const vt of expandTransition(transition)) {
2139
+ const baseSucc = computeSuccessor(net, current.base, vt, envPlaces, envMode);
2140
+ if (baseSucc === null || baseSucc.isEmpty()) continue;
2141
+ const nameSuccs = nameSuccessors(role, current.names, vt.outputPlaces, fragment, sym);
2142
+ for (const nm of nameSuccs) {
2143
+ const succ = new NameStateClass(baseSucc, nm, fragment.colouredOrder);
2144
+ let toIdx = indexOf.get(succ.key);
2145
+ if (toIdx === void 0) {
2146
+ toIdx = graph.classes.length;
2147
+ graph.pushClass(succ, indexOf);
2148
+ queue.push(toIdx);
2149
+ }
2150
+ graph.addEdge(curIdx, toIdx, transition.name);
2151
+ }
2152
+ }
2153
+ }
2154
+ }
2155
+ return graph;
2156
+ }
2157
+ pushClass(c, indexOf) {
2158
+ const idx = this.classes.length;
2159
+ this.classes.push(c);
2160
+ this._successors.push([]);
2161
+ indexOf.set(c.key, idx);
2162
+ }
2163
+ addEdge(from, to, name) {
2164
+ this.edges.push({ from, to, transitionName: name });
2165
+ this._successors[from].push(to);
2166
+ }
2167
+ };
2168
+ function nameSuccessors(role, names, outputPlaces, fragment, sym) {
2169
+ switch (role.type) {
2170
+ case "ordinary":
2171
+ return [names.copy()];
2172
+ case "mint": {
2173
+ const colouredOut = [...outputPlaces].filter((p) => fragment.isColoured(p.name)).map((p) => p.name);
2174
+ const nm = names.copy();
2175
+ if (colouredOut.length > 0) {
2176
+ const fresh = sym.next++;
2177
+ for (const p of colouredOut) nm.add(p, fresh, 1);
2178
+ }
2179
+ return [nm];
2180
+ }
2181
+ case "join": {
2182
+ const result = [];
2183
+ for (const s of enablingSymbols(names, role.colouredIn)) {
2184
+ const nm = names.copy();
2185
+ for (const [p, req] of role.colouredIn) nm.remove(p, s, req);
2186
+ result.push(nm);
2187
+ }
2188
+ return result;
2189
+ }
2190
+ }
2191
+ }
2192
+ function enablingSymbols(names, colouredIn) {
2193
+ if (colouredIn.length === 0) return [];
2194
+ const [firstPlace, firstReq] = colouredIn[0];
2195
+ const result = [];
2196
+ for (const s of names.symbolsIn(firstPlace)) {
2197
+ if (names.countOf(firstPlace, s) < firstReq) continue;
2198
+ let ok = true;
2199
+ for (let i = 1; i < colouredIn.length; i++) {
2200
+ const [p, req] = colouredIn[i];
2201
+ if (names.countOf(p, s) < req) {
2202
+ ok = false;
2203
+ break;
2204
+ }
2205
+ }
2206
+ if (ok) result.push(s);
2207
+ }
2208
+ return result;
2209
+ }
2210
+
2211
+ // src/verification/nu-scg-verifier.ts
2212
+ var NOTE_EXACT = "\nNote: \u03BD-join correlation decided exactly via the state-class-graph name-partition quotient \u2014 the symbolic graph closed, so the verdict is sound AND complete (no spurious different-name counterexample; quiescence is name-aware), beyond the bounded-budget fragment (NU-050, Route B).\n";
2213
+ function verifyViaNameScg(net, initial, property, sinkPlaces, environmentPlaces, environmentMode, maxClasses) {
2214
+ const fragment = classify(net);
2215
+ if (fragment === null) return null;
2216
+ for (const p of initial.placesWithTokens()) {
2217
+ if (fragment.isColoured(p.name)) return null;
2218
+ }
2219
+ const scg = NameStateClassGraph.build(net, initial, fragment, maxClasses, environmentPlaces, environmentMode);
2220
+ if (!scg.isComplete()) {
2221
+ return {
2222
+ verdict: {
2223
+ type: "unknown",
2224
+ reason: `\u03BD name-aware state-class graph truncated at ${maxClasses} classes \u2014 the live correlation pool is not structurally bounded; reachability over unbounded fresh names is undecidable (NU-050, Route B). Declare a budget place to bound the live pool, or raise nuMaxClasses.`
2225
+ },
2226
+ trace: [],
2227
+ transitions: [],
2228
+ note: "",
2229
+ classCount: scg.classCount()
2230
+ };
2231
+ }
2232
+ const violating = decide(scg, property, sinkPlaces);
2233
+ if (violating >= 0) {
2234
+ const [trace, transitions] = counterexamplePath(scg, violating);
2235
+ return { verdict: { type: "violated" }, trace, transitions, note: NOTE_EXACT, classCount: scg.classCount() };
2236
+ }
2237
+ return {
2238
+ verdict: { type: "proven", method: "\u03BD name-partition SCG (NU-050, Route B)", inductiveInvariant: null },
2239
+ trace: [],
2240
+ transitions: [],
2241
+ note: NOTE_EXACT,
2242
+ classCount: scg.classCount()
2243
+ };
2244
+ }
2245
+ function decide(scg, property, sinkPlaces) {
2246
+ const firstWhere = (pred) => {
2247
+ for (let i = 0; i < scg.classCount(); i++) {
2248
+ if (pred(i)) return i;
2249
+ }
2250
+ return -1;
2251
+ };
2252
+ switch (property.type) {
2253
+ case "place-bound":
2254
+ case "branch-place-bound":
2255
+ return firstWhere((i) => scg.markingOf(i).tokens(property.place) > property.bound);
2256
+ case "unreachable":
2257
+ return firstWhere((i) => {
2258
+ const m = scg.markingOf(i);
2259
+ for (const p of property.places) {
2260
+ if (!m.hasTokens(p)) return false;
2261
+ }
2262
+ return true;
2263
+ });
2264
+ case "mutual-exclusion":
2265
+ return firstWhere((i) => {
2266
+ const m = scg.markingOf(i);
2267
+ return m.hasTokens(property.p1) && m.hasTokens(property.p2);
2268
+ });
2269
+ case "deadlock-free":
2270
+ return firstWhere((i) => scg.successorsOf(i).length === 0 && !allTokensInSinks(scg.markingOf(i), sinkPlaces));
2271
+ case "joined-or-dead-lettered":
2272
+ return firstWhere((i) => scg.successorsOf(i).length === 0 && scg.markingOf(i).hasTokens(property.pending));
2273
+ }
2274
+ }
2275
+ function allTokensInSinks(m, sinks) {
2276
+ const sinkNames = /* @__PURE__ */ new Set();
2277
+ for (const s of sinks) sinkNames.add(s.name);
2278
+ for (const p of m.placesWithTokens()) {
2279
+ if (!sinkNames.has(p.name)) return false;
2280
+ }
2281
+ return true;
2282
+ }
2283
+ function counterexamplePath(scg, target) {
2284
+ const n = scg.classCount();
2285
+ const parent = new Array(n).fill(-1);
2286
+ const via = new Array(n).fill("");
2287
+ const visited = new Array(n).fill(false);
2288
+ visited[0] = true;
2289
+ const queue = [0];
2290
+ while (queue.length > 0) {
2291
+ const u = queue.shift();
2292
+ if (u === target) break;
2293
+ for (const e of scg.edges) {
2294
+ if (e.from === u && !visited[e.to]) {
2295
+ visited[e.to] = true;
2296
+ parent[e.to] = u;
2297
+ via[e.to] = e.transitionName;
2298
+ queue.push(e.to);
2299
+ }
2300
+ }
2301
+ }
2302
+ const chain = [];
2303
+ for (let cur = target; cur !== -1; cur = parent[cur]) {
2304
+ chain.push(cur);
2305
+ }
2306
+ chain.reverse();
2307
+ const markings = chain.map((i) => scg.markingOf(i));
2308
+ const transitions = chain.slice(1).map((i) => via[i]);
2309
+ return [markings, transitions];
2310
+ }
2311
+
2312
+ // src/verification/smt-verifier.ts
2313
+ var SmtVerifier = class _SmtVerifier {
2314
+ constructor(net) {
2315
+ this.net = net;
2316
+ }
2317
+ _initialMarking = MarkingState.empty();
2318
+ _property = deadlockFree();
2319
+ _environmentPlaces = /* @__PURE__ */ new Set();
2320
+ _sinkPlaces = /* @__PURE__ */ new Set();
2321
+ _budgetPlaces = /* @__PURE__ */ new Set();
2322
+ _environmentMode = alwaysAvailable();
2323
+ _timeoutMs = 6e4;
2324
+ _nuMaxClasses = 1e5;
2325
+ static forNet(net) {
2326
+ return new _SmtVerifier(net);
2327
+ }
2328
+ initialMarking(arg) {
2329
+ if (arg instanceof MarkingState) {
2330
+ this._initialMarking = arg;
2331
+ } else {
2332
+ const builder = MarkingState.builder();
2333
+ arg(builder);
2334
+ this._initialMarking = builder.build();
2335
+ }
2336
+ return this;
2337
+ }
2338
+ property(property) {
2339
+ this._property = property;
2340
+ return this;
2341
+ }
2342
+ environmentPlaces(...places) {
2343
+ for (const p of places) this._environmentPlaces.add(p);
2344
+ return this;
2345
+ }
2346
+ environmentMode(mode) {
2347
+ this._environmentMode = mode;
2348
+ return this;
2349
+ }
2350
+ /**
2351
+ * Declares expected sink (terminal) places for deadlock-freedom analysis.
2352
+ * Markings where any sink place has a token are not considered deadlocks.
2353
+ */
2354
+ sinkPlaces(...places) {
2355
+ for (const p of places) this._sinkPlaces.add(p);
2356
+ return this;
2357
+ }
2358
+ /**
2359
+ * Declares ν-net budget places (NU-040): places whose token count bounds the
2360
+ * live correlation pool (they gate fresh-name minting). Declaring at least one
2361
+ * places the net in the decidable bounded fragment, so reachability-safety
2362
+ * properties over its ν-joins are verified (the matched transitions are
2363
+ * over-approximated). Without any budget place, a net that mints fresh names
2364
+ * is treated as unbounded and the verifier returns `unknown` (NU-050).
2365
+ */
2366
+ budgetPlaces(...places) {
2367
+ for (const p of places) this._budgetPlaces.add(p.name);
2368
+ return this;
2369
+ }
2370
+ timeout(ms) {
2371
+ this._timeoutMs = ms;
2372
+ return this;
2373
+ }
2374
+ /**
2375
+ * Sets the class-count cap for the ν-aware state-class-graph analysis (NU-050,
2376
+ * Route B). When the symbolic name-aware graph would exceed this, the analysis
2377
+ * truncates and the verdict is `unknown` (the live correlation pool is not
2378
+ * structurally bounded). Default 100_000.
2379
+ */
2380
+ nuMaxClasses(max) {
2381
+ this._nuMaxClasses = max;
2382
+ return this;
2383
+ }
2384
+ /**
2385
+ * Runs the verification pipeline.
2386
+ */
2387
+ async verify() {
2388
+ const start = performance.now();
2389
+ const report = [];
2390
+ report.push("=== IC3/PDR SAFETY VERIFICATION ===\n");
2391
+ report.push(`Net: ${this.net.name}`);
2392
+ const propDesc = this._sinkPlaces.size === 0 ? propertyDescription(this._property) : `${propertyDescription(this._property)} (sinks: ${[...this._sinkPlaces].map((p) => p.name).join(", ")})`;
2393
+ report.push(`Property: ${propDesc}`);
2394
+ report.push(`Timeout: ${(this._timeoutMs / 1e3).toFixed(0)}s
2395
+ `);
2396
+ const hasMatch = [...this.net.transitions].some((t) => t.matchSpec !== null);
2397
+ const nuBounded = this._budgetPlaces.size > 0;
2398
+ if (hasMatch && (!isReachabilitySafety(this._property) || !nuBounded)) {
2399
+ const outcome = verifyViaNameScg(
2400
+ this.net,
2401
+ this._initialMarking,
2402
+ this._property,
2403
+ this._sinkPlaces,
2404
+ this._environmentPlaces,
2405
+ this._environmentMode,
2406
+ this._nuMaxClasses
2407
+ );
2408
+ if (outcome !== null) {
2409
+ report.push("=== \u03BD-net Route B: name-aware state-class graph (NU-050) ===");
2410
+ report.push(` Name-partition state classes: ${outcome.classCount}`);
2411
+ report.push(outcome.note);
2412
+ if (outcome.transitions.length > 0) {
2413
+ report.push(` Counterexample trace: ${outcome.trace.length} states, ${outcome.transitions.length} transitions`);
2414
+ }
2415
+ return buildResult(
2416
+ outcome.verdict,
2417
+ report.join("\n"),
2418
+ [],
2419
+ [],
2420
+ outcome.trace,
2421
+ outcome.transitions,
2422
+ performance.now() - start,
2423
+ {
2424
+ places: [...this.net.places].length,
2425
+ transitions: [...this.net.transitions].length,
2426
+ invariantsFound: 0,
2427
+ structuralResult: "n/a (\u03BD name-partition SCG)"
2428
+ }
2429
+ );
2430
+ }
2431
+ }
2432
+ report.push("Phase 1: Flattening net...");
2433
+ const flatNet = flatten(this.net, this._environmentPlaces, this._environmentMode);
2434
+ report.push(` Places: ${flatNet.places.length}`);
2435
+ report.push(` Transitions (expanded): ${flatNet.transitions.length}`);
2436
+ if (flatNet.environmentBounds.size > 0) {
2437
+ report.push(` Environment bounds: ${flatNet.environmentBounds.size} places`);
2438
+ }
2439
+ report.push("");
2440
+ report.push("Phase 2: Structural pre-check (siphon/trap)...");
2441
+ const structResult = structuralCheck(flatNet, this._initialMarking);
2442
+ let structResultStr;
2443
+ switch (structResult.type) {
2444
+ case "no-potential-deadlock":
2445
+ structResultStr = "no potential deadlock";
2446
+ break;
2447
+ case "potential-deadlock":
2448
+ structResultStr = `potential deadlock (siphon: {${[...structResult.siphon].join(",")}})`;
2449
+ break;
2450
+ case "inconclusive":
2451
+ structResultStr = `inconclusive (${structResult.reason})`;
2452
+ break;
2453
+ }
2454
+ report.push(` Result: ${structResultStr}
2455
+ `);
2456
+ if (this._property.type === "deadlock-free" && !hasMatch && this._sinkPlaces.size === 0 && structResult.type === "no-potential-deadlock" && this._environmentPlaces.size === 0) {
2457
+ report.push("=== RESULT ===\n");
2458
+ report.push("PROVEN (structural): Deadlock-freedom verified by Commoner's theorem.");
2459
+ report.push(" All siphons contain initially marked traps.");
2460
+ return buildResult(
2461
+ { type: "proven", method: "structural", inductiveInvariant: null },
2462
+ report.join("\n"),
2463
+ [],
2464
+ [],
2465
+ [],
2466
+ [],
2467
+ performance.now() - start,
2468
+ { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: 0, structuralResult: structResultStr }
2469
+ );
2470
+ }
2471
+ report.push("Phase 3: Computing P-invariants...");
2472
+ const matrix = IncidenceMatrix.from(flatNet);
2473
+ const invariants = computePInvariants(matrix, flatNet, this._initialMarking);
2474
+ report.push(` Found: ${invariants.length} P-invariant(s)`);
2475
+ const structurallyBounded = isCoveredByInvariants(invariants, flatNet.places.length);
2476
+ report.push(` Structurally bounded: ${structurallyBounded ? "YES" : "NO"}`);
2477
+ for (const inv of invariants) {
2478
+ report.push(` ${formatInvariant(inv, flatNet)}`);
2479
+ }
2480
+ report.push("");
2481
+ report.push("Phase 4: IC3/PDR verification via Z3 Spacer...");
2482
+ const colouredPlan = hasMatch && nuBounded && isReachabilitySafety(this._property) ? buildColouredPlan(this.net, flatNet, this._initialMarking, this._budgetPlaces) : null;
2483
+ let runner;
2484
+ try {
2485
+ runner = await createSpacerRunner(this._timeoutMs);
2486
+ } catch (e) {
2487
+ report.push(` ERROR: ${e.message ?? e}
2488
+ `);
2489
+ report.push("=== RESULT ===\n");
2490
+ report.push(`UNKNOWN: Z3 initialization error: ${e.message ?? e}`);
2491
+ return buildResult(
2492
+ { type: "unknown", reason: `Z3 init error: ${e.message ?? e}` },
2493
+ report.join("\n"),
2494
+ invariants,
2495
+ [],
2496
+ [],
2497
+ [],
2498
+ performance.now() - start,
2499
+ { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
2500
+ );
2501
+ }
2502
+ try {
2503
+ let encoding;
2504
+ if (colouredPlan != null) {
2505
+ report.push(
2506
+ ` \u03BD-encoding: name-coloured (exact within budget k=${colouredPlan.k}; ${colouredPlan.coloured.length} coloured place(s))`
2507
+ );
2508
+ encoding = encodeColoured(runner.ctx, runner.fp, colouredPlan, flatNet, this._initialMarking, this._property, invariants);
2509
+ } else {
2510
+ encoding = encode(runner.ctx, runner.fp, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
2511
+ }
2512
+ const queryResult = await runner.query(encoding.errorExpr, encoding.reachableDecl);
2513
+ switch (queryResult.type) {
2514
+ case "proven": {
2515
+ if (this._environmentPlaces.size > 0 && this._environmentMode.type === "ignore") {
2516
+ const reason = "environment places present but not modeled (mode=ignore); a proof would be vacuous \u2014 use alwaysAvailable() or bounded(k) to model external injection";
2517
+ report.push(` Status: UNSAT, but vacuous under ignore mode
2518
+ `);
2519
+ report.push("=== RESULT ===\n");
2520
+ report.push(`UNKNOWN: ${reason}`);
2521
+ return buildResult(
2522
+ { type: "unknown", reason },
2523
+ report.join("\n"),
2524
+ invariants,
2525
+ [],
2526
+ [],
2527
+ [],
2528
+ performance.now() - start,
2529
+ { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
2530
+ );
2531
+ }
2532
+ report.push(" Status: UNSAT (property holds)\n");
2533
+ const discoveredInvariants = [];
2534
+ if (queryResult.invariantFormula != null) {
2535
+ discoveredInvariants.push(substituteNames(queryResult.invariantFormula, flatNet));
2536
+ }
2537
+ for (const level of queryResult.levelInvariants) {
2538
+ discoveredInvariants.push(substituteNames(level, flatNet));
2539
+ }
2540
+ if (discoveredInvariants.length > 0) {
2541
+ report.push("Phase 5: Inductive invariant (discovered by IC3)");
2542
+ report.push(` Spacer synthesized: ${discoveredInvariants[0]}`);
2543
+ report.push(" This formula is INDUCTIVE: preserved by all transitions.");
2544
+ if (discoveredInvariants.length > 1) {
2545
+ report.push(" Per-level clauses:");
2546
+ for (let i = 1; i < discoveredInvariants.length; i++) {
2547
+ report.push(` ${discoveredInvariants[i]}`);
2548
+ }
2549
+ }
2550
+ report.push("");
2551
+ }
2552
+ report.push("=== RESULT ===\n");
2553
+ report.push(`PROVEN (IC3/PDR): ${propDesc}`);
2554
+ report.push(" Z3 Spacer proved no reachable state violates the property.");
2555
+ report.push(" NOTE: Verification ignores timing constraints and JS guards.");
2556
+ report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
2557
+ return this.applyNuGuard(buildResult(
2558
+ {
2559
+ type: "proven",
2560
+ method: "IC3/PDR",
2561
+ inductiveInvariant: queryResult.invariantFormula != null ? substituteNames(queryResult.invariantFormula, flatNet) : null
2562
+ },
2563
+ report.join("\n"),
2564
+ invariants,
2565
+ discoveredInvariants,
2566
+ [],
2567
+ [],
2568
+ performance.now() - start,
2569
+ { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
2570
+ ), hasMatch, nuBounded, colouredPlan != null);
2571
+ }
2572
+ case "violated": {
2573
+ report.push(" Status: SAT (counterexample found)\n");
2574
+ const decoded = decode(runner.ctx, queryResult.answer, flatNet);
2575
+ report.push("=== RESULT ===\n");
2576
+ report.push(`VIOLATED: ${propDesc}`);
2577
+ if (decoded.trace.length > 0) {
2578
+ report.push(` Counterexample trace (${decoded.trace.length} states):`);
2579
+ for (let i = 0; i < decoded.trace.length; i++) {
2580
+ report.push(` ${i}: ${decoded.trace[i]}`);
2581
+ }
2582
+ }
2583
+ if (decoded.transitions.length > 0) {
2584
+ report.push(` Firing sequence: ${decoded.transitions.join(" -> ")}`);
2585
+ }
2586
+ report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
2587
+ report.push(" It may be spurious if timing constraints prevent this sequence.");
2588
+ report.push(" JS guards are also ignored in this analysis.");
2589
+ return this.applyNuGuard(buildResult(
2590
+ { type: "violated" },
2591
+ report.join("\n"),
2592
+ invariants,
2593
+ [],
2594
+ decoded.trace,
2595
+ decoded.transitions,
2596
+ performance.now() - start,
2597
+ { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
2598
+ ), hasMatch, nuBounded, colouredPlan != null);
2599
+ }
2600
+ case "unknown": {
2601
+ report.push(` Status: UNKNOWN (${queryResult.reason})
2602
+ `);
2603
+ report.push("=== RESULT ===\n");
2604
+ report.push(`UNKNOWN: Could not determine ${propDesc}`);
2605
+ report.push(` Reason: ${queryResult.reason}`);
2606
+ return buildResult(
2607
+ { type: "unknown", reason: queryResult.reason },
2608
+ report.join("\n"),
2609
+ invariants,
2610
+ [],
2611
+ [],
2612
+ [],
2613
+ performance.now() - start,
2614
+ { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
2615
+ );
2616
+ }
2617
+ }
2618
+ } catch (e) {
2619
+ report.push(` ERROR: ${e.message ?? e}
2620
+ `);
2621
+ report.push("=== RESULT ===\n");
2622
+ report.push(`UNKNOWN: Z3 solver error: ${e.message ?? e}`);
2623
+ return buildResult(
2624
+ { type: "unknown", reason: `Z3 error: ${e.message ?? e}` },
2625
+ report.join("\n"),
2626
+ invariants,
2627
+ [],
2628
+ [],
2629
+ [],
2630
+ performance.now() - start,
2631
+ { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
2632
+ );
2633
+ } finally {
2634
+ runner.dispose();
2635
+ }
2636
+ }
2637
+ /**
2638
+ * ν-net soundness guard (NU-040, NU-050). Applied only when the net contains
2639
+ * match (ν-join) transitions, and only to a proven/violated verdict (an
2640
+ * existing unknown is left as-is).
2641
+ *
2642
+ * - Quiescence-based properties (deadlock / joined-or-dead-lettered): the
2643
+ * name-blind over-approximation over-fires joins, so it sees fewer quiescent
2644
+ * states and may miss a real stranded marking — downgraded to unknown
2645
+ * (exact quiescence reasoning is deferred to the SCG name-partition quotient).
2646
+ * - Reachability-safety with unbounded fresh names (no budget declared):
2647
+ * reachability over unbounded fresh names is undecidable — unknown.
2648
+ * - Bounded reachability-safety in the name-coloured fragment (`exact`): name
2649
+ * equality is encoded exactly via bounded name-colouring, so the verdict is
2650
+ * sound *and* complete within the budget — no spurious different-name
2651
+ * counterexample. The verdict is kept and the exact-path note is appended.
2652
+ * - Bounded reachability-safety outside that fragment: `proven` is sound; a
2653
+ * `violated` may be spurious — the verdict is kept and the over-approximation
2654
+ * caveat is appended to the report.
2655
+ */
2656
+ applyNuGuard(result, hasMatch, nuBounded, exact) {
2657
+ if (!hasMatch || result.verdict.type === "unknown") return result;
2658
+ if (!isReachabilitySafety(this._property)) {
2659
+ return downgradeToUnknown(
2660
+ result,
2661
+ "\u03BD-matching transitions present and the property depends on quiescence (deadlock / joined-or-dead-lettered); the name-blind over-approximation cannot decide it soundly \u2014 deferred to the exact \u03BD-analysis (NU-050)"
2662
+ );
2663
+ }
2664
+ if (!nuBounded) {
2665
+ return downgradeToUnknown(
2666
+ result,
2667
+ "\u03BD-matching transitions present with unbounded fresh names (no budget place declared via budgetPlaces(...)); reachability over unbounded fresh names is undecidable (NU-040) \u2014 declare the budget place(s) that gate minting to verify within the bounded fragment"
2668
+ );
2669
+ }
2670
+ const note = exact ? "\nNote: \u03BD-join name equality is encoded exactly via bounded name-colouring (k = budget); the verdict is sound and complete within the budget bound \u2014 no spurious different-name counterexample (NU-050 #1).\n" : "\nNote: matched (\u03BD-join) transitions are over-approximated (name equality assumed satisfiable). 'proven' is sound; a 'violated' counterexample may be spurious pending the exact \u03BD-analysis (NU-050).\n";
2671
+ return { ...result, report: result.report + note };
2672
+ }
2673
+ };
2674
+ function isReachabilitySafety(property) {
2675
+ switch (property.type) {
2676
+ case "place-bound":
2677
+ case "branch-place-bound":
2678
+ case "mutual-exclusion":
2679
+ case "unreachable":
2680
+ return true;
2681
+ case "deadlock-free":
2682
+ case "joined-or-dead-lettered":
2683
+ return false;
2684
+ }
2685
+ }
2686
+ function downgradeToUnknown(result, reason) {
2687
+ return {
2688
+ ...result,
2689
+ verdict: { type: "unknown", reason },
2690
+ report: result.report + `
2691
+ Downgraded to UNKNOWN: ${reason}
2692
+ `,
2693
+ discoveredInvariants: [],
2694
+ counterexampleTrace: [],
2695
+ counterexampleTransitions: []
2696
+ };
2697
+ }
2698
+ function substituteNames(formula, flatNet) {
2699
+ for (let i = flatNet.places.length - 1; i >= 0; i--) {
2700
+ formula = formula.replace(new RegExp(`\\bm${i}\\b`, "g"), flatNet.places[i].name);
2701
+ }
2702
+ return formula;
2703
+ }
2704
+ function formatInvariant(inv, flatNet) {
2705
+ const parts = [];
2706
+ for (const idx of inv.support) {
2707
+ if (inv.weights[idx] !== 1) {
2708
+ parts.push(`${inv.weights[idx]}*${flatNet.places[idx].name}`);
2709
+ } else {
2710
+ parts.push(flatNet.places[idx].name);
2711
+ }
2712
+ }
2713
+ return `${parts.join(" + ")} = ${inv.constant}`;
2714
+ }
2715
+ function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics) {
2716
+ return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, elapsedMs, statistics };
2717
+ }
2718
+
2719
+ // src/verification/smt-verification-result.ts
2720
+ function isProven(result) {
2721
+ return result.verdict.type === "proven";
2722
+ }
2723
+ function isViolated(result) {
2724
+ return result.verdict.type === "violated";
2725
+ }
2726
+
2727
+ export {
2728
+ and,
2729
+ andPlaces,
2730
+ xor,
2731
+ xorPlaces,
2732
+ outPlace,
2733
+ timeout,
2734
+ timeoutPlace,
2735
+ forwardInput,
2736
+ allPlaces,
2737
+ enumerateBranches,
2738
+ MarkingState,
2739
+ MarkingStateBuilder,
2740
+ deadlockFree,
2741
+ mutualExclusion,
2742
+ placeBound,
2743
+ unreachable,
2744
+ branchPlaceBound,
2745
+ joinedOrDeadLettered,
2746
+ propertyDescription,
2747
+ flatTransition,
2748
+ alwaysAvailable,
2749
+ bounded,
2750
+ ignore,
2751
+ flatten,
2752
+ IncidenceMatrix,
2753
+ pInvariant,
2754
+ pInvariantToString,
2755
+ computePInvariants,
2756
+ isCoveredByInvariants,
2757
+ structuralCheck,
2758
+ findMinimalSiphons,
2759
+ findMaximalTrapIn,
2760
+ createSpacerRunner,
2761
+ flatNetPlaceCount,
2762
+ flatNetTransitionCount,
2763
+ flatNetIndexOf,
2764
+ encode,
2765
+ DBM,
2766
+ StateClass,
2767
+ StateClassGraph,
2768
+ decode,
2769
+ SmtVerifier,
2770
+ isProven,
2771
+ isViolated
2772
+ };
2773
+ //# sourceMappingURL=chunk-M3KT7BFO.js.map