libpetri 0.5.3 → 0.6.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.
@@ -2,6 +2,10 @@ import {
2
2
  allPlaces,
3
3
  enumerateBranches
4
4
  } from "../chunk-VQ4XMJTD.js";
5
+ import {
6
+ earliest,
7
+ latest
8
+ } from "../chunk-FN773SSE.js";
5
9
 
6
10
  // src/verification/marking-state.ts
7
11
  var MARKING_STATE_KEY = /* @__PURE__ */ Symbol("MarkingState.internal");
@@ -80,6 +84,31 @@ var MarkingStateBuilder = class {
80
84
  }
81
85
  return this;
82
86
  }
87
+ /** Removes tokens from a place. Throws if insufficient. */
88
+ removeTokens(place, count) {
89
+ const current = this.tokenCounts.get(place.name) ?? 0;
90
+ const newCount = current - count;
91
+ if (newCount < 0) {
92
+ throw new Error(
93
+ `Cannot remove ${count} tokens from ${place.name} (has ${current})`
94
+ );
95
+ }
96
+ if (newCount === 0) {
97
+ this.tokenCounts.delete(place.name);
98
+ this.placesByName.delete(place.name);
99
+ } else {
100
+ this.tokenCounts.set(place.name, newCount);
101
+ }
102
+ return this;
103
+ }
104
+ /** Copies all token counts from another marking state. */
105
+ copyFrom(other) {
106
+ for (const p of other.placesWithTokens()) {
107
+ this.tokenCounts.set(p.name, other.tokens(p));
108
+ this.placesByName.set(p.name, p);
109
+ }
110
+ return this;
111
+ }
83
112
  build() {
84
113
  return new MarkingState(MARKING_STATE_KEY, new Map(this.tokenCounts), new Map(this.placesByName));
85
114
  }
@@ -1190,19 +1219,892 @@ function formatInvariant(inv, flatNet) {
1190
1219
  function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics) {
1191
1220
  return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, elapsedMs, statistics };
1192
1221
  }
1222
+
1223
+ // src/verification/analysis/dbm.ts
1224
+ var EPSILON = 1e-9;
1225
+ var DBM = class _DBM {
1226
+ bounds;
1227
+ dim;
1228
+ clockNames;
1229
+ _empty;
1230
+ constructor(bounds, dim, clockNames, empty) {
1231
+ this.bounds = bounds;
1232
+ this.dim = dim;
1233
+ this.clockNames = clockNames;
1234
+ this._empty = empty;
1235
+ }
1236
+ /** Creates an initial firing domain for enabled transitions. */
1237
+ static create(clockNames, lowerBounds, upperBounds) {
1238
+ const n = clockNames.length;
1239
+ const dim = n + 1;
1240
+ const bounds = makeMatrix(dim, Infinity);
1241
+ for (let i = 0; i < n; i++) {
1242
+ bounds[0 * dim + (i + 1)] = -lowerBounds[i];
1243
+ bounds[(i + 1) * dim + 0] = upperBounds[i];
1244
+ }
1245
+ return new _DBM(bounds, dim, clockNames, false).canonicalize();
1246
+ }
1247
+ /** Creates an empty (unsatisfiable) zone. */
1248
+ static empty(clockNames) {
1249
+ const b = new Float64Array(1);
1250
+ b[0] = 0;
1251
+ return new _DBM(b, 1, clockNames, true);
1252
+ }
1253
+ isEmpty() {
1254
+ return this._empty;
1255
+ }
1256
+ clockCount() {
1257
+ return this.clockNames.length;
1258
+ }
1259
+ get(i, j) {
1260
+ return this.bounds[i * this.dim + j];
1261
+ }
1262
+ /** Gets the lower bound (earliest firing time) for clock i. */
1263
+ getLowerBound(clockIndex) {
1264
+ if (this._empty || clockIndex < 0 || clockIndex >= this.clockNames.length) return 0;
1265
+ const val = -this.get(0, clockIndex + 1);
1266
+ return val === 0 ? 0 : val;
1267
+ }
1268
+ /** Gets the upper bound (latest firing time / deadline) for clock i. */
1269
+ getUpperBound(clockIndex) {
1270
+ if (this._empty || clockIndex < 0 || clockIndex >= this.clockNames.length) return Infinity;
1271
+ return this.get(clockIndex + 1, 0);
1272
+ }
1273
+ /** Checks if transition can fire (lower bound <= 0 after time passage). */
1274
+ canFire(clockIndex) {
1275
+ return !this._empty && this.getLowerBound(clockIndex) <= EPSILON;
1276
+ }
1277
+ /**
1278
+ * Computes the successor firing domain after firing transition t_f.
1279
+ * Implements the 5-step Berthomieu-Diaz successor formula.
1280
+ */
1281
+ fireTransition(firedClock, newClockNames, newLowerBounds, newUpperBounds, persistentClocks) {
1282
+ if (this._empty) return this;
1283
+ const n = this.clockNames.length;
1284
+ if (firedClock < 0 || firedClock >= n) {
1285
+ throw new Error(`Invalid fired clock index: ${firedClock}`);
1286
+ }
1287
+ const constrained = new Float64Array(this.bounds);
1288
+ const dim = this.dim;
1289
+ const f = firedClock + 1;
1290
+ for (let i = 0; i < n; i++) {
1291
+ if (i !== firedClock) {
1292
+ const idx = i + 1;
1293
+ const pos = f * dim + idx;
1294
+ constrained[pos] = Math.min(constrained[pos], 0);
1295
+ }
1296
+ }
1297
+ if (!canonicalizeInPlace(constrained, dim)) {
1298
+ return _DBM.empty([]);
1299
+ }
1300
+ const newN = persistentClocks.length + newClockNames.length;
1301
+ const newDim = newN + 1;
1302
+ const newBounds = makeMatrix(newDim, Infinity);
1303
+ for (let pi = 0; pi < persistentClocks.length; pi++) {
1304
+ const oldIdx = persistentClocks[pi] + 1;
1305
+ const newIdx = pi + 1;
1306
+ const upper = constrained[oldIdx * dim + f];
1307
+ const lower = Math.max(0, -constrained[f * dim + oldIdx]);
1308
+ newBounds[0 * newDim + newIdx] = -lower;
1309
+ newBounds[newIdx * newDim + 0] = upper;
1310
+ for (let pj = 0; pj < persistentClocks.length; pj++) {
1311
+ const oldJ = persistentClocks[pj] + 1;
1312
+ const newJ = pj + 1;
1313
+ newBounds[newIdx * newDim + newJ] = constrained[oldIdx * dim + oldJ];
1314
+ }
1315
+ }
1316
+ const offset = persistentClocks.length;
1317
+ for (let k = 0; k < newClockNames.length; k++) {
1318
+ const idx = offset + k + 1;
1319
+ newBounds[0 * newDim + idx] = -newLowerBounds[k];
1320
+ newBounds[idx * newDim + 0] = newUpperBounds[k];
1321
+ }
1322
+ const allNames = [];
1323
+ for (const idx of persistentClocks) {
1324
+ allNames.push(this.clockNames[idx]);
1325
+ }
1326
+ allNames.push(...newClockNames);
1327
+ return new _DBM(newBounds, newDim, allNames, false).canonicalize();
1328
+ }
1329
+ /** Lets time pass: set all lower bounds to 0. */
1330
+ letTimePass() {
1331
+ if (this._empty) return this;
1332
+ const newBounds = new Float64Array(this.bounds);
1333
+ for (let i = 1; i < this.dim; i++) {
1334
+ newBounds[0 * this.dim + i] = 0;
1335
+ }
1336
+ return new _DBM(newBounds, this.dim, this.clockNames, false).canonicalize();
1337
+ }
1338
+ canonicalize() {
1339
+ if (this._empty) return this;
1340
+ const canon = new Float64Array(this.bounds);
1341
+ if (!canonicalizeInPlace(canon, this.dim)) {
1342
+ return _DBM.empty(this.clockNames);
1343
+ }
1344
+ return new _DBM(canon, this.dim, this.clockNames, false);
1345
+ }
1346
+ equals(other) {
1347
+ if (this === other) return true;
1348
+ if (this._empty && other._empty) return true;
1349
+ if (this._empty || other._empty) return false;
1350
+ if (this.clockNames.length !== other.clockNames.length) return false;
1351
+ for (let i = 0; i < this.clockNames.length; i++) {
1352
+ if (this.clockNames[i] !== other.clockNames[i]) return false;
1353
+ }
1354
+ if (this.bounds.length !== other.bounds.length) return false;
1355
+ for (let i = 0; i < this.bounds.length; i++) {
1356
+ if (Math.abs(this.bounds[i] - other.bounds[i]) > EPSILON) return false;
1357
+ }
1358
+ return true;
1359
+ }
1360
+ toString() {
1361
+ if (this._empty) return "DBM[empty]";
1362
+ const parts = [];
1363
+ for (let i = 0; i < this.clockNames.length; i++) {
1364
+ const lo = formatBound(this.getLowerBound(i));
1365
+ const hi = formatBound(this.getUpperBound(i));
1366
+ parts.push(`${this.clockNames[i]}:[${lo},${hi}]`);
1367
+ }
1368
+ return `DBM{${parts.join(", ")}}`;
1369
+ }
1370
+ };
1371
+ function makeMatrix(dim, fill) {
1372
+ const m = new Float64Array(dim * dim).fill(fill);
1373
+ for (let i = 0; i < dim; i++) {
1374
+ m[i * dim + i] = 0;
1375
+ }
1376
+ return m;
1377
+ }
1378
+ function canonicalizeInPlace(dbm, dim) {
1379
+ for (let k = 0; k < dim; k++) {
1380
+ for (let i = 0; i < dim; i++) {
1381
+ for (let j = 0; j < dim; j++) {
1382
+ const ik = dbm[i * dim + k];
1383
+ const kj = dbm[k * dim + j];
1384
+ if (ik < Infinity && kj < Infinity) {
1385
+ const via = ik + kj;
1386
+ if (via < dbm[i * dim + j]) {
1387
+ dbm[i * dim + j] = via;
1388
+ }
1389
+ }
1390
+ }
1391
+ }
1392
+ }
1393
+ for (let i = 0; i < dim; i++) {
1394
+ if (dbm[i * dim + i] < -EPSILON) return false;
1395
+ }
1396
+ return true;
1397
+ }
1398
+ function formatBound(b) {
1399
+ if (b >= Infinity / 2) return "\u221E";
1400
+ if (b === Math.trunc(b)) return String(b);
1401
+ return b.toFixed(3);
1402
+ }
1403
+
1404
+ // src/verification/analysis/state-class.ts
1405
+ var StateClass = class {
1406
+ marking;
1407
+ firingDomain;
1408
+ enabledTransitions;
1409
+ constructor(marking, firingDomain, enabledTransitions) {
1410
+ this.marking = marking;
1411
+ this.firingDomain = firingDomain;
1412
+ this.enabledTransitions = [...enabledTransitions];
1413
+ }
1414
+ isEmpty() {
1415
+ return this.firingDomain.isEmpty();
1416
+ }
1417
+ canFire(transition) {
1418
+ const idx = this.enabledTransitions.indexOf(transition);
1419
+ if (idx < 0) return false;
1420
+ return this.firingDomain.getUpperBound(idx) >= 0;
1421
+ }
1422
+ transitionIndex(transition) {
1423
+ return this.enabledTransitions.indexOf(transition);
1424
+ }
1425
+ equals(other) {
1426
+ if (this === other) return true;
1427
+ return this.marking.toString() === other.marking.toString() && this.firingDomain.equals(other.firingDomain);
1428
+ }
1429
+ toString() {
1430
+ return `StateClass{${this.marking}, ${this.firingDomain}}`;
1431
+ }
1432
+ };
1433
+
1434
+ // src/verification/analysis/scc-analyzer.ts
1435
+ function computeSCCs(nodes, successors) {
1436
+ const nodeArray = [...nodes];
1437
+ const indexMap = /* @__PURE__ */ new Map();
1438
+ const lowlink = /* @__PURE__ */ new Map();
1439
+ const onStack = /* @__PURE__ */ new Set();
1440
+ const stack = [];
1441
+ const sccs = [];
1442
+ let index = 0;
1443
+ function strongConnect(v) {
1444
+ indexMap.set(v, index);
1445
+ lowlink.set(v, index);
1446
+ index++;
1447
+ stack.push(v);
1448
+ onStack.add(v);
1449
+ for (const w of successors(v)) {
1450
+ if (!indexMap.has(w)) {
1451
+ strongConnect(w);
1452
+ lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
1453
+ } else if (onStack.has(w)) {
1454
+ lowlink.set(v, Math.min(lowlink.get(v), indexMap.get(w)));
1455
+ }
1456
+ }
1457
+ if (lowlink.get(v) === indexMap.get(v)) {
1458
+ const scc = /* @__PURE__ */ new Set();
1459
+ let w;
1460
+ do {
1461
+ w = stack.pop();
1462
+ onStack.delete(w);
1463
+ scc.add(w);
1464
+ } while (w !== v);
1465
+ sccs.push(scc);
1466
+ }
1467
+ }
1468
+ for (const node of nodeArray) {
1469
+ if (!indexMap.has(node)) {
1470
+ strongConnect(node);
1471
+ }
1472
+ }
1473
+ return sccs;
1474
+ }
1475
+ function findTerminalSCCs(nodes, successors) {
1476
+ const allSCCs = computeSCCs(nodes, successors);
1477
+ const terminal = [];
1478
+ for (const scc of allSCCs) {
1479
+ let isTerminal = true;
1480
+ for (const node of scc) {
1481
+ for (const succ of successors(node)) {
1482
+ if (!scc.has(succ)) {
1483
+ isTerminal = false;
1484
+ break;
1485
+ }
1486
+ }
1487
+ if (!isTerminal) break;
1488
+ }
1489
+ if (isTerminal) {
1490
+ terminal.push(scc);
1491
+ }
1492
+ }
1493
+ return terminal;
1494
+ }
1495
+
1496
+ // src/verification/analysis/environment-analysis-mode.ts
1497
+ function alwaysAvailable() {
1498
+ return { type: "always-available" };
1499
+ }
1500
+ function bounded2(maxTokens) {
1501
+ if (maxTokens < 0) throw new Error("maxTokens must be non-negative");
1502
+ return { type: "bounded", maxTokens };
1503
+ }
1504
+ function ignore() {
1505
+ return { type: "ignore" };
1506
+ }
1507
+
1508
+ // src/verification/analysis/state-class-graph.ts
1509
+ var StateClassGraph = class _StateClassGraph {
1510
+ net;
1511
+ initialClass;
1512
+ _stateClasses;
1513
+ _transitions;
1514
+ _successors;
1515
+ _predecessors;
1516
+ _complete;
1517
+ constructor(net, initialClass, stateClasses, transitions, complete) {
1518
+ this.net = net;
1519
+ this.initialClass = initialClass;
1520
+ this._stateClasses = stateClasses;
1521
+ this._transitions = transitions;
1522
+ this._complete = complete;
1523
+ this._successors = /* @__PURE__ */ new Map();
1524
+ this._predecessors = /* @__PURE__ */ new Map();
1525
+ for (const sc of stateClasses) {
1526
+ this._successors.set(sc, /* @__PURE__ */ new Set());
1527
+ this._predecessors.set(sc, /* @__PURE__ */ new Set());
1528
+ }
1529
+ for (const [from, tMap] of transitions) {
1530
+ for (const edges of tMap.values()) {
1531
+ for (const edge of edges) {
1532
+ this._successors.get(from).add(edge.target);
1533
+ this._predecessors.get(edge.target).add(from);
1534
+ }
1535
+ }
1536
+ }
1537
+ }
1538
+ /** Builds the state class graph for a Time Petri Net. */
1539
+ static build(net, initialMarking, maxClasses, environmentPlaces, environmentMode) {
1540
+ const envMode = environmentMode ?? ignore();
1541
+ const envPlaces = /* @__PURE__ */ new Set();
1542
+ if (environmentPlaces) {
1543
+ for (const ep of environmentPlaces) {
1544
+ envPlaces.add(ep.place);
1545
+ }
1546
+ }
1547
+ const enabledTransitions = findEnabledTransitions(net, initialMarking, envPlaces, envMode);
1548
+ const clockNames = enabledTransitions.map((t) => t.name);
1549
+ const lowerBounds = enabledTransitions.map((t) => earliest(t.timing) / 1e3);
1550
+ const upperBounds = enabledTransitions.map((t) => latest(t.timing) / 1e3);
1551
+ let initialDBM = DBM.create(clockNames, lowerBounds, upperBounds);
1552
+ initialDBM = initialDBM.letTimePass();
1553
+ const initialClass = new StateClass(initialMarking, initialDBM, enabledTransitions);
1554
+ const stateClasses = [initialClass];
1555
+ const stateClassSet = /* @__PURE__ */ new Set([classKey(initialClass)]);
1556
+ const classMap = /* @__PURE__ */ new Map([[classKey(initialClass), initialClass]]);
1557
+ const transitionMap = /* @__PURE__ */ new Map();
1558
+ transitionMap.set(initialClass, /* @__PURE__ */ new Map());
1559
+ const queue = [initialClass];
1560
+ let complete = true;
1561
+ while (queue.length > 0) {
1562
+ if (stateClasses.length >= maxClasses) {
1563
+ complete = false;
1564
+ break;
1565
+ }
1566
+ const current = queue.shift();
1567
+ for (const transition of current.enabledTransitions) {
1568
+ const virtualTransitions = expandTransition(transition);
1569
+ for (const vt of virtualTransitions) {
1570
+ const successor = computeSuccessor(net, current, vt, envPlaces, envMode);
1571
+ if (successor === null || successor.isEmpty()) continue;
1572
+ const tEdges = transitionMap.get(current);
1573
+ if (!tEdges.has(transition)) tEdges.set(transition, []);
1574
+ tEdges.get(transition).push({ branchIndex: vt.branchIndex, target: successor });
1575
+ const key = classKey(successor);
1576
+ if (!stateClassSet.has(key)) {
1577
+ stateClassSet.add(key);
1578
+ classMap.set(key, successor);
1579
+ stateClasses.push(successor);
1580
+ transitionMap.set(successor, /* @__PURE__ */ new Map());
1581
+ queue.push(successor);
1582
+ } else {
1583
+ const canonical = classMap.get(key);
1584
+ if (canonical !== successor) {
1585
+ const edges = tEdges.get(transition);
1586
+ edges[edges.length - 1] = { branchIndex: vt.branchIndex, target: canonical };
1587
+ }
1588
+ }
1589
+ }
1590
+ }
1591
+ }
1592
+ return new _StateClassGraph(net, initialClass, stateClasses, transitionMap, complete);
1593
+ }
1594
+ stateClasses() {
1595
+ return this._stateClasses;
1596
+ }
1597
+ size() {
1598
+ return this._stateClasses.length;
1599
+ }
1600
+ isComplete() {
1601
+ return this._complete;
1602
+ }
1603
+ successors(sc) {
1604
+ return this._successors.get(sc) ?? /* @__PURE__ */ new Set();
1605
+ }
1606
+ predecessors(sc) {
1607
+ return this._predecessors.get(sc) ?? /* @__PURE__ */ new Set();
1608
+ }
1609
+ /** Returns all outgoing transitions with their branch edges. */
1610
+ outgoingBranchEdges(sc) {
1611
+ return this._transitions.get(sc) ?? /* @__PURE__ */ new Map();
1612
+ }
1613
+ /** Returns the branch edges for a specific transition from a state class. */
1614
+ branchEdges(sc, transition) {
1615
+ const map = this._transitions.get(sc);
1616
+ if (!map) return [];
1617
+ return map.get(transition) ?? [];
1618
+ }
1619
+ /** Returns all transitions that are enabled from a state class. */
1620
+ enabledTransitions(sc) {
1621
+ const map = this._transitions.get(sc);
1622
+ if (!map) return /* @__PURE__ */ new Set();
1623
+ return new Set(map.keys());
1624
+ }
1625
+ /** Finds all state classes with a given marking. */
1626
+ classesWithMarking(marking) {
1627
+ const key = marking.toString();
1628
+ return this._stateClasses.filter((sc) => sc.marking.toString() === key);
1629
+ }
1630
+ /** Checks if a marking is reachable. */
1631
+ isReachable(marking) {
1632
+ const key = marking.toString();
1633
+ return this._stateClasses.some((sc) => sc.marking.toString() === key);
1634
+ }
1635
+ /** Gets all reachable markings. */
1636
+ reachableMarkings() {
1637
+ const markings = /* @__PURE__ */ new Set();
1638
+ for (const sc of this._stateClasses) {
1639
+ markings.add(sc.marking.toString());
1640
+ }
1641
+ return markings;
1642
+ }
1643
+ /** Counts edges in the graph (each branch edge counts separately). */
1644
+ edgeCount() {
1645
+ let count = 0;
1646
+ for (const map of this._transitions.values()) {
1647
+ for (const edges of map.values()) {
1648
+ count += edges.length;
1649
+ }
1650
+ }
1651
+ return count;
1652
+ }
1653
+ toString() {
1654
+ return `StateClassGraph[classes=${this.size()}, edges=${this.edgeCount()}, complete=${this._complete}]`;
1655
+ }
1656
+ };
1657
+ function classKey(sc) {
1658
+ return `${sc.marking.toString()}|${sc.firingDomain.toString()}`;
1659
+ }
1660
+ function expandTransition(t) {
1661
+ let branches;
1662
+ if (t.outputSpec !== null) {
1663
+ branches = enumerateBranches(t.outputSpec);
1664
+ } else {
1665
+ branches = [/* @__PURE__ */ new Set()];
1666
+ }
1667
+ return branches.map((outputPlaces, i) => ({
1668
+ transition: t,
1669
+ branchIndex: i,
1670
+ outputPlaces
1671
+ }));
1672
+ }
1673
+ function computeSuccessor(net, current, fired, environmentPlaces, environmentMode) {
1674
+ const transition = fired.transition;
1675
+ const newMarking = fireTransition(current.marking, transition, fired.outputPlaces, environmentPlaces, environmentMode);
1676
+ const newEnabledAll = findEnabledTransitions(net, newMarking, environmentPlaces, environmentMode);
1677
+ const persistent = [];
1678
+ const persistentIndices = [];
1679
+ for (let i = 0; i < current.enabledTransitions.length; i++) {
1680
+ const t = current.enabledTransitions[i];
1681
+ if (t !== transition && newEnabledAll.includes(t)) {
1682
+ persistent.push(t);
1683
+ persistentIndices.push(i);
1684
+ }
1685
+ }
1686
+ const newlyEnabled = [];
1687
+ for (const t of newEnabledAll) {
1688
+ if (!persistent.includes(t)) {
1689
+ newlyEnabled.push(t);
1690
+ }
1691
+ }
1692
+ const firedIdx = current.transitionIndex(transition);
1693
+ const newClockNames = newlyEnabled.map((t) => t.name);
1694
+ const newLowerBounds = newlyEnabled.map((t) => earliest(t.timing) / 1e3);
1695
+ const newUpperBounds = newlyEnabled.map((t) => latest(t.timing) / 1e3);
1696
+ let newDBM = current.firingDomain.fireTransition(
1697
+ firedIdx,
1698
+ newClockNames,
1699
+ newLowerBounds,
1700
+ newUpperBounds,
1701
+ persistentIndices
1702
+ );
1703
+ newDBM = newDBM.letTimePass();
1704
+ const allEnabled = [...persistent, ...newlyEnabled];
1705
+ return new StateClass(newMarking, newDBM, allEnabled);
1706
+ }
1707
+ function findEnabledTransitions(net, marking, environmentPlaces, environmentMode) {
1708
+ const enabled = [];
1709
+ for (const transition of net.transitions) {
1710
+ if (isEnabled(transition, marking, environmentPlaces, environmentMode)) {
1711
+ enabled.push(transition);
1712
+ }
1713
+ }
1714
+ return enabled;
1715
+ }
1716
+ function isEnabled(transition, marking, environmentPlaces, environmentMode) {
1717
+ for (const spec of transition.inputSpecs) {
1718
+ const required = inputRequiredCount(spec);
1719
+ if (!checkPlaceEnabled(spec.place, required, marking, environmentPlaces, environmentMode)) {
1720
+ return false;
1721
+ }
1722
+ }
1723
+ for (const arc of transition.reads) {
1724
+ if (!checkPlaceEnabled(arc.place, 1, marking, environmentPlaces, environmentMode)) {
1725
+ return false;
1726
+ }
1727
+ }
1728
+ for (const arc of transition.inhibitors) {
1729
+ if (marking.hasTokens(arc.place)) {
1730
+ return false;
1731
+ }
1732
+ }
1733
+ return true;
1734
+ }
1735
+ function inputRequiredCount(spec) {
1736
+ switch (spec.type) {
1737
+ case "one":
1738
+ return 1;
1739
+ case "exactly":
1740
+ return spec.count;
1741
+ case "all":
1742
+ return 1;
1743
+ case "at-least":
1744
+ return spec.minimum;
1745
+ }
1746
+ }
1747
+ function inputConsumeCount(spec) {
1748
+ switch (spec.type) {
1749
+ case "one":
1750
+ return 1;
1751
+ case "exactly":
1752
+ return spec.count;
1753
+ case "all":
1754
+ return 1;
1755
+ // Analysis: consume minimum (1 token)
1756
+ case "at-least":
1757
+ return spec.minimum;
1758
+ }
1759
+ }
1760
+ function checkPlaceEnabled(place, required, marking, environmentPlaces, environmentMode) {
1761
+ if (!environmentPlaces.has(place)) {
1762
+ return marking.tokens(place) >= required;
1763
+ }
1764
+ switch (environmentMode.type) {
1765
+ case "always-available":
1766
+ return true;
1767
+ case "bounded":
1768
+ return required <= environmentMode.maxTokens;
1769
+ case "ignore":
1770
+ return marking.tokens(place) >= required;
1771
+ }
1772
+ }
1773
+ function fireTransition(marking, transition, outputPlaces, environmentPlaces, environmentMode) {
1774
+ const builder = MarkingState.builder().copyFrom(marking);
1775
+ for (const spec of transition.inputSpecs) {
1776
+ const toConsume = inputConsumeCount(spec);
1777
+ consumeFromPlace(builder, spec.place, toConsume, environmentPlaces, environmentMode);
1778
+ }
1779
+ for (const arc of transition.resets) {
1780
+ const current = marking.tokens(arc.place);
1781
+ if (current > 0) {
1782
+ builder.removeTokens(arc.place, current);
1783
+ }
1784
+ }
1785
+ for (const place of outputPlaces) {
1786
+ builder.addTokens(place, 1);
1787
+ }
1788
+ return builder.build();
1789
+ }
1790
+ function consumeFromPlace(builder, place, count, environmentPlaces, environmentMode) {
1791
+ if (!environmentPlaces.has(place)) {
1792
+ builder.removeTokens(place, count);
1793
+ return;
1794
+ }
1795
+ if (environmentMode.type === "ignore") {
1796
+ builder.removeTokens(place, count);
1797
+ }
1798
+ }
1799
+
1800
+ // src/verification/analysis/time-petri-net-analyzer.ts
1801
+ var TimePetriNetAnalyzer = class _TimePetriNetAnalyzer {
1802
+ net;
1803
+ initialMarking;
1804
+ goalPlaces;
1805
+ maxClasses;
1806
+ environmentPlaces;
1807
+ environmentMode;
1808
+ /** @internal Called by builder — use `TimePetriNetAnalyzer.forNet()` instead. */
1809
+ static create(net, initialMarking, goalPlaces, maxClasses, environmentPlaces, environmentMode) {
1810
+ return new _TimePetriNetAnalyzer(net, initialMarking, goalPlaces, maxClasses, environmentPlaces, environmentMode);
1811
+ }
1812
+ constructor(net, initialMarking, goalPlaces, maxClasses, environmentPlaces, environmentMode) {
1813
+ this.net = net;
1814
+ this.initialMarking = initialMarking;
1815
+ this.goalPlaces = goalPlaces;
1816
+ this.maxClasses = maxClasses;
1817
+ this.environmentPlaces = environmentPlaces;
1818
+ this.environmentMode = environmentMode;
1819
+ }
1820
+ static forNet(net) {
1821
+ return new TimePetriNetAnalyzerBuilder(net);
1822
+ }
1823
+ /** Performs formal liveness analysis. */
1824
+ analyze() {
1825
+ const report = [];
1826
+ report.push("=== TIME PETRI NET FORMAL ANALYSIS ===\n");
1827
+ report.push(`Method: State Class Graph (Berthomieu-Diaz 1991)`);
1828
+ report.push(`Net: ${this.net.name}`);
1829
+ report.push(`Places: ${this.net.places.size}`);
1830
+ report.push(`Transitions: ${this.net.transitions.size}`);
1831
+ report.push(`Goal places: [${[...this.goalPlaces].map((p) => p.name).join(", ")}]
1832
+ `);
1833
+ report.push("Phase 1: Building State Class Graph...");
1834
+ if (this.environmentPlaces.size > 0) {
1835
+ report.push(` Environment places: ${this.environmentPlaces.size}`);
1836
+ report.push(` Environment mode: ${this.environmentMode.type}`);
1837
+ }
1838
+ const scg = StateClassGraph.build(this.net, this.initialMarking, this.maxClasses, this.environmentPlaces, this.environmentMode);
1839
+ report.push(` State classes: ${scg.size()}`);
1840
+ report.push(` Edges: ${scg.edgeCount()}`);
1841
+ report.push(` Complete: ${scg.isComplete() ? "YES" : "NO (truncated)"}`);
1842
+ if (!scg.isComplete()) {
1843
+ report.push(` WARNING: State class graph truncated at ${this.maxClasses} classes. Analysis may be incomplete.`);
1844
+ }
1845
+ report.push("");
1846
+ report.push("Phase 2: Identifying goal state classes...");
1847
+ const goalClasses = /* @__PURE__ */ new Set();
1848
+ for (const sc of scg.stateClasses()) {
1849
+ if (sc.marking.hasTokensInAny(this.goalPlaces)) {
1850
+ goalClasses.add(sc);
1851
+ }
1852
+ }
1853
+ report.push(` Goal state classes: ${goalClasses.size}
1854
+ `);
1855
+ report.push("Phase 3: Computing Strongly Connected Components...");
1856
+ const successorFn = (sc) => scg.successors(sc);
1857
+ const allSCCs = computeSCCs(scg.stateClasses(), successorFn);
1858
+ const terminalSCCs = findTerminalSCCs(scg.stateClasses(), successorFn);
1859
+ report.push(` Total SCCs: ${allSCCs.length}`);
1860
+ report.push(` Terminal SCCs: ${terminalSCCs.length}
1861
+ `);
1862
+ report.push("Phase 4: Verifying Goal Liveness...");
1863
+ report.push(" Property: From every reachable state, a goal state is reachable");
1864
+ const terminalSCCsWithGoal = [];
1865
+ const terminalSCCsWithoutGoal = [];
1866
+ for (const scc of terminalSCCs) {
1867
+ let hasGoal = false;
1868
+ for (const sc of scc) {
1869
+ if (goalClasses.has(sc)) {
1870
+ hasGoal = true;
1871
+ break;
1872
+ }
1873
+ }
1874
+ (hasGoal ? terminalSCCsWithGoal : terminalSCCsWithoutGoal).push(scc);
1875
+ }
1876
+ report.push(` Terminal SCCs with goal: ${terminalSCCsWithGoal.length}`);
1877
+ report.push(` Terminal SCCs without goal: ${terminalSCCsWithoutGoal.length}`);
1878
+ const canReachGoal = computeBackwardReachability(scg, goalClasses);
1879
+ const statesNotReachingGoal = scg.size() - canReachGoal.size;
1880
+ report.push(` States that can reach goal: ${canReachGoal.size}/${scg.size()}
1881
+ `);
1882
+ const isGoalLive = terminalSCCsWithoutGoal.length === 0 && statesNotReachingGoal === 0;
1883
+ report.push("Phase 5: Verifying Classical Liveness (L4)...");
1884
+ report.push(" Property: Every transition can fire from every reachable marking");
1885
+ const allTransitions = new Set(this.net.transitions);
1886
+ const terminalSCCsMissingTransitions = [];
1887
+ for (const scc of terminalSCCs) {
1888
+ const transitionsInSCC = /* @__PURE__ */ new Set();
1889
+ for (const sc of scc) {
1890
+ for (const t of scg.enabledTransitions(sc)) {
1891
+ const edges = scg.branchEdges(sc, t);
1892
+ for (const edge of edges) {
1893
+ if (scc.has(edge.target)) {
1894
+ transitionsInSCC.add(t);
1895
+ }
1896
+ }
1897
+ }
1898
+ }
1899
+ let missingAny = false;
1900
+ for (const t of allTransitions) {
1901
+ if (!transitionsInSCC.has(t)) {
1902
+ missingAny = true;
1903
+ break;
1904
+ }
1905
+ }
1906
+ if (missingAny) {
1907
+ terminalSCCsMissingTransitions.push(scc);
1908
+ const missing = [...allTransitions].filter((t) => !transitionsInSCC.has(t));
1909
+ report.push(` Terminal SCC missing transitions: [${missing.map((t) => t.name).join(", ")}]`);
1910
+ }
1911
+ }
1912
+ const isL4Live = terminalSCCsMissingTransitions.length === 0 && scg.isComplete();
1913
+ report.push("\n=== ANALYSIS RESULT ===\n");
1914
+ if (isGoalLive && scg.isComplete()) {
1915
+ report.push("GOAL LIVENESS VERIFIED");
1916
+ report.push(" From every reachable state class, a goal marking is reachable.");
1917
+ } else if (isGoalLive && !scg.isComplete()) {
1918
+ report.push("GOAL LIVENESS LIKELY (incomplete proof)");
1919
+ } else {
1920
+ report.push("GOAL LIVENESS VIOLATION");
1921
+ if (terminalSCCsWithoutGoal.length > 0) {
1922
+ report.push(` ${terminalSCCsWithoutGoal.length} terminal SCC(s) have no goal state.`);
1923
+ }
1924
+ if (statesNotReachingGoal > 0) {
1925
+ report.push(` ${statesNotReachingGoal} state class(es) cannot reach goal.`);
1926
+ }
1927
+ }
1928
+ report.push("");
1929
+ if (isL4Live) {
1930
+ report.push("CLASSICAL LIVENESS (L4) VERIFIED");
1931
+ } else {
1932
+ report.push("CLASSICAL LIVENESS (L4) NOT VERIFIED");
1933
+ if (terminalSCCsMissingTransitions.length > 0) {
1934
+ report.push(" Some terminal SCCs don't contain all transitions.");
1935
+ }
1936
+ if (!scg.isComplete()) {
1937
+ report.push(" (State class graph incomplete - cannot prove L4)");
1938
+ }
1939
+ }
1940
+ return {
1941
+ stateClassGraph: scg,
1942
+ allSCCs,
1943
+ terminalSCCs,
1944
+ goalClasses,
1945
+ canReachGoal,
1946
+ isGoalLive,
1947
+ isL4Live,
1948
+ isComplete: scg.isComplete(),
1949
+ report: report.join("\n")
1950
+ };
1951
+ }
1952
+ /** Analyzes XOR branch coverage for a built state class graph. */
1953
+ static analyzeXorBranches(scg) {
1954
+ const result = /* @__PURE__ */ new Map();
1955
+ for (const transition of scg.net.transitions) {
1956
+ if (transition.outputSpec === null) continue;
1957
+ const allBranches = enumerateBranches(transition.outputSpec);
1958
+ if (allBranches.length <= 1) continue;
1959
+ const takenBranches = /* @__PURE__ */ new Set();
1960
+ for (const sc of scg.stateClasses()) {
1961
+ const edges = scg.branchEdges(sc, transition);
1962
+ for (const edge of edges) {
1963
+ takenBranches.add(edge.branchIndex);
1964
+ }
1965
+ }
1966
+ const untakenBranches = /* @__PURE__ */ new Set();
1967
+ for (let i = 0; i < allBranches.length; i++) {
1968
+ if (!takenBranches.has(i)) untakenBranches.add(i);
1969
+ }
1970
+ result.set(transition, {
1971
+ totalBranches: allBranches.length,
1972
+ takenBranches,
1973
+ untakenBranches,
1974
+ branchOutputs: allBranches
1975
+ });
1976
+ }
1977
+ return createXorBranchAnalysis(result);
1978
+ }
1979
+ };
1980
+ function createXorBranchAnalysis(transitionBranches) {
1981
+ return {
1982
+ transitionBranches,
1983
+ unreachableBranches() {
1984
+ const result = /* @__PURE__ */ new Map();
1985
+ for (const [t, info] of transitionBranches) {
1986
+ if (info.untakenBranches.size > 0) {
1987
+ result.set(t, info.untakenBranches);
1988
+ }
1989
+ }
1990
+ return result;
1991
+ },
1992
+ isXorComplete() {
1993
+ for (const info of transitionBranches.values()) {
1994
+ if (info.untakenBranches.size > 0) return false;
1995
+ }
1996
+ return true;
1997
+ },
1998
+ report() {
1999
+ if (transitionBranches.size === 0) return "No XOR transitions in net.";
2000
+ const lines = [];
2001
+ lines.push("XOR Branch Coverage Analysis");
2002
+ lines.push("============================\n");
2003
+ for (const [t, info] of transitionBranches) {
2004
+ lines.push(`Transition: ${t.name}`);
2005
+ lines.push(` Branches: ${info.totalBranches}`);
2006
+ lines.push(` Taken: [${[...info.takenBranches].join(", ")}]`);
2007
+ if (info.untakenBranches.size > 0) {
2008
+ lines.push(` UNREACHABLE: [${[...info.untakenBranches].join(", ")}]`);
2009
+ for (const idx of info.untakenBranches) {
2010
+ const places = [...info.branchOutputs[idx]].map((p) => p.name).join(", ");
2011
+ lines.push(` Branch ${idx} outputs: [${places}]`);
2012
+ }
2013
+ } else {
2014
+ lines.push(" All branches reachable");
2015
+ }
2016
+ lines.push("");
2017
+ }
2018
+ if (this.isXorComplete()) {
2019
+ lines.push("RESULT: All XOR branches are reachable.");
2020
+ } else {
2021
+ lines.push("RESULT: Some XOR branches are unreachable!");
2022
+ }
2023
+ return lines.join("\n");
2024
+ }
2025
+ };
2026
+ }
2027
+ function computeBackwardReachability(scg, goals) {
2028
+ const reachable = new Set(goals);
2029
+ const queue = [...goals];
2030
+ while (queue.length > 0) {
2031
+ const current = queue.shift();
2032
+ for (const pred of scg.predecessors(current)) {
2033
+ if (!reachable.has(pred)) {
2034
+ reachable.add(pred);
2035
+ queue.push(pred);
2036
+ }
2037
+ }
2038
+ }
2039
+ return reachable;
2040
+ }
2041
+ var TimePetriNetAnalyzerBuilder = class {
2042
+ net;
2043
+ _initialMarking = MarkingState.empty();
2044
+ _goalPlaces = /* @__PURE__ */ new Set();
2045
+ _maxClasses = 1e5;
2046
+ _environmentPlaces = /* @__PURE__ */ new Set();
2047
+ _environmentMode = ignore();
2048
+ constructor(net) {
2049
+ this.net = net;
2050
+ }
2051
+ initialMarking(marking) {
2052
+ this._initialMarking = marking;
2053
+ return this;
2054
+ }
2055
+ goalPlaces(...places) {
2056
+ for (const p of places) this._goalPlaces.add(p);
2057
+ return this;
2058
+ }
2059
+ maxClasses(max) {
2060
+ this._maxClasses = max;
2061
+ return this;
2062
+ }
2063
+ environmentPlaces(...places) {
2064
+ for (const ep of places) this._environmentPlaces.add(ep);
2065
+ return this;
2066
+ }
2067
+ environmentMode(mode) {
2068
+ this._environmentMode = mode;
2069
+ return this;
2070
+ }
2071
+ build() {
2072
+ if (this._goalPlaces.size === 0) {
2073
+ throw new Error("At least one goal place must be specified");
2074
+ }
2075
+ return TimePetriNetAnalyzer.create(
2076
+ this.net,
2077
+ this._initialMarking,
2078
+ this._goalPlaces,
2079
+ this._maxClasses,
2080
+ this._environmentPlaces,
2081
+ this._environmentMode
2082
+ );
2083
+ }
2084
+ };
1193
2085
  export {
2086
+ DBM,
1194
2087
  IncidenceMatrix,
1195
2088
  MarkingState,
1196
2089
  MarkingStateBuilder,
1197
2090
  SmtVerifier,
2091
+ StateClass,
2092
+ StateClassGraph,
2093
+ TimePetriNetAnalyzer,
2094
+ TimePetriNetAnalyzerBuilder,
2095
+ alwaysAvailable as analysisAlwaysAvailable,
2096
+ bounded2 as analysisBounded,
2097
+ ignore as analysisIgnore,
1198
2098
  bounded,
1199
2099
  computePInvariants,
2100
+ computeSCCs,
1200
2101
  createSpacerRunner,
1201
2102
  deadlockFree,
1202
2103
  decode,
1203
2104
  encode,
1204
2105
  findMaximalTrapIn,
1205
2106
  findMinimalSiphons,
2107
+ findTerminalSCCs,
1206
2108
  flatNetIndexOf,
1207
2109
  flatNetPlaceCount,
1208
2110
  flatNetTransitionCount,