libpetri 2.10.5 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-M3KT7BFO.js → chunk-V3WTQRHC.js} +499 -64
- package/dist/chunk-V3WTQRHC.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/verification/index.d.ts +131 -2
- package/dist/verification/index.js +3 -1
- package/dist/verification/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-M3KT7BFO.js.map +0 -1
|
@@ -588,6 +588,90 @@ function computePInvariants(matrix, flatNet, initialMarking) {
|
|
|
588
588
|
}
|
|
589
589
|
return invariants;
|
|
590
590
|
}
|
|
591
|
+
function computePSemiflows(matrix, flatNet, initialMarking) {
|
|
592
|
+
const np = matrix.numPlaces();
|
|
593
|
+
const nt = matrix.numTransitions();
|
|
594
|
+
if (np === 0) return [];
|
|
595
|
+
const incidence = matrix.incidence();
|
|
596
|
+
let rows = [];
|
|
597
|
+
for (let p = 0; p < np; p++) {
|
|
598
|
+
const sig = new Array(nt);
|
|
599
|
+
for (let t = 0; t < nt; t++) sig[t] = incidence[t][p];
|
|
600
|
+
const weight = new Array(np).fill(0);
|
|
601
|
+
weight[p] = 1;
|
|
602
|
+
rows.push({ sig, weight });
|
|
603
|
+
}
|
|
604
|
+
for (let t = 0; t < nt; t++) {
|
|
605
|
+
const next = rows.filter((r) => r.sig[t] === 0);
|
|
606
|
+
const pos = rows.filter((r) => r.sig[t] > 0);
|
|
607
|
+
const neg = rows.filter((r) => r.sig[t] < 0);
|
|
608
|
+
for (const rp of pos) {
|
|
609
|
+
for (const rn of neg) {
|
|
610
|
+
const cp = -rn.sig[t];
|
|
611
|
+
const cn = rp.sig[t];
|
|
612
|
+
const sig = combineRow(cp, rp.sig, cn, rn.sig);
|
|
613
|
+
const weight = combineRow(cp, rp.weight, cn, rn.weight);
|
|
614
|
+
if (sig === null || weight === null) continue;
|
|
615
|
+
reduceGcd(sig, weight);
|
|
616
|
+
next.push({ sig, weight });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
rows = keepSupportMinimal(next);
|
|
620
|
+
if (rows.length > 8192) rows.length = 8192;
|
|
621
|
+
}
|
|
622
|
+
const semiflows = [];
|
|
623
|
+
for (const { weight } of rows) {
|
|
624
|
+
if (!weight.some((x) => x !== 0)) continue;
|
|
625
|
+
const support = /* @__PURE__ */ new Set();
|
|
626
|
+
let constant = 0;
|
|
627
|
+
for (let p = 0; p < np; p++) {
|
|
628
|
+
if (weight[p] !== 0) {
|
|
629
|
+
support.add(p);
|
|
630
|
+
constant += weight[p] * initialMarking.tokens(flatNet.places[p]);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (!Number.isSafeInteger(constant)) continue;
|
|
634
|
+
semiflows.push(pInvariant(weight, constant, support));
|
|
635
|
+
}
|
|
636
|
+
return semiflows;
|
|
637
|
+
}
|
|
638
|
+
function combineRow(cp, a, cn, b) {
|
|
639
|
+
const out = new Array(a.length);
|
|
640
|
+
for (let i = 0; i < a.length; i++) {
|
|
641
|
+
const v = cp * a[i] + cn * b[i];
|
|
642
|
+
if (!Number.isSafeInteger(v)) return null;
|
|
643
|
+
out[i] = v;
|
|
644
|
+
}
|
|
645
|
+
return out;
|
|
646
|
+
}
|
|
647
|
+
function reduceGcd(sig, weight) {
|
|
648
|
+
let g = 0;
|
|
649
|
+
for (const v of sig) g = gcd(g, Math.abs(v));
|
|
650
|
+
for (const v of weight) g = gcd(g, Math.abs(v));
|
|
651
|
+
if (g > 1) {
|
|
652
|
+
for (let i = 0; i < sig.length; i++) sig[i] = sig[i] / g;
|
|
653
|
+
for (let i = 0; i < weight.length; i++) weight[i] = weight[i] / g;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function keepSupportMinimal(rows) {
|
|
657
|
+
const supports = rows.map((r) => {
|
|
658
|
+
const s = [];
|
|
659
|
+
for (let i = 0; i < r.weight.length; i++) if (r.weight[i] !== 0) s.push(i);
|
|
660
|
+
return s;
|
|
661
|
+
});
|
|
662
|
+
const keep = new Array(rows.length).fill(true);
|
|
663
|
+
for (let i = 0; i < rows.length; i++) {
|
|
664
|
+
if (!keep[i]) continue;
|
|
665
|
+
for (let j = 0; j < rows.length; j++) {
|
|
666
|
+
if (i === j || !keep[j]) continue;
|
|
667
|
+
if (supports[j].length < supports[i].length && supports[j].every((p) => supports[i].includes(p))) {
|
|
668
|
+
keep[i] = false;
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return rows.filter((_, i) => keep[i]);
|
|
674
|
+
}
|
|
591
675
|
function isCoveredByInvariants(invariants, numPlaces) {
|
|
592
676
|
const covered = new Array(numPlaces).fill(false);
|
|
593
677
|
for (const inv of invariants) {
|
|
@@ -1277,10 +1361,24 @@ var StateClass = class {
|
|
|
1277
1361
|
marking;
|
|
1278
1362
|
firingDomain;
|
|
1279
1363
|
enabledTransitions;
|
|
1280
|
-
|
|
1364
|
+
/**
|
|
1365
|
+
* Class-relative earliest-ready time (seconds) of each enabled transition,
|
|
1366
|
+
* parallel to `enabledTransitions`. Captured from the firing-domain DBM
|
|
1367
|
+
* (`getLowerBound(k)`) *before* `letTimePass()` zeroes the lower bounds, i.e.
|
|
1368
|
+
* the minimum time from class entry at which clock `k` may fire.
|
|
1369
|
+
*
|
|
1370
|
+
* Purely additive: base timed-reachability (marking + DBM zone, `equals`,
|
|
1371
|
+
* `classKey`) ignores it. Read only by the ν conflict-priority prune (NU-052,
|
|
1372
|
+
* `priorityDominated`), where comparing `readyEarliest[H] <= readyEarliest[L]`
|
|
1373
|
+
* decides whether the strictly higher-priority `H` becomes ready no later than
|
|
1374
|
+
* `L` and so pre-empts it.
|
|
1375
|
+
*/
|
|
1376
|
+
readyEarliest;
|
|
1377
|
+
constructor(marking, firingDomain, enabledTransitions, readyEarliest) {
|
|
1281
1378
|
this.marking = marking;
|
|
1282
1379
|
this.firingDomain = firingDomain;
|
|
1283
1380
|
this.enabledTransitions = [...enabledTransitions];
|
|
1381
|
+
this.readyEarliest = [...readyEarliest];
|
|
1284
1382
|
}
|
|
1285
1383
|
isEmpty() {
|
|
1286
1384
|
return this.firingDomain.isEmpty();
|
|
@@ -1453,9 +1551,10 @@ function initialStateClass(net, initialMarking, envPlaces, envMode) {
|
|
|
1453
1551
|
const clockNames = enabledTransitions.map((t) => t.name);
|
|
1454
1552
|
const lowerBounds = enabledTransitions.map((t) => earliest(t.timing) / 1e3);
|
|
1455
1553
|
const upperBounds = enabledTransitions.map((t) => latest(t.timing) / 1e3);
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1554
|
+
const baseDBM = DBM.create(clockNames, lowerBounds, upperBounds);
|
|
1555
|
+
const readyEarliest = enabledTransitions.map((_, k) => baseDBM.getLowerBound(k));
|
|
1556
|
+
const initialDBM = baseDBM.letTimePass();
|
|
1557
|
+
return new StateClass(initialMarking, initialDBM, enabledTransitions, readyEarliest);
|
|
1459
1558
|
}
|
|
1460
1559
|
function expandTransition(t) {
|
|
1461
1560
|
let branches;
|
|
@@ -1493,16 +1592,17 @@ function computeSuccessor(net, current, fired, environmentPlaces, environmentMod
|
|
|
1493
1592
|
const newClockNames = newlyEnabled.map((t) => t.name);
|
|
1494
1593
|
const newLowerBounds = newlyEnabled.map((t) => earliest(t.timing) / 1e3);
|
|
1495
1594
|
const newUpperBounds = newlyEnabled.map((t) => latest(t.timing) / 1e3);
|
|
1496
|
-
|
|
1595
|
+
const firedDBM = current.firingDomain.fireTransition(
|
|
1497
1596
|
firedIdx,
|
|
1498
1597
|
newClockNames,
|
|
1499
1598
|
newLowerBounds,
|
|
1500
1599
|
newUpperBounds,
|
|
1501
1600
|
persistentIndices
|
|
1502
1601
|
);
|
|
1503
|
-
newDBM = newDBM.letTimePass();
|
|
1504
1602
|
const allEnabled = [...persistent, ...newlyEnabled];
|
|
1505
|
-
|
|
1603
|
+
const readyEarliest = allEnabled.map((_, k) => firedDBM.getLowerBound(k));
|
|
1604
|
+
const newDBM = firedDBM.letTimePass();
|
|
1605
|
+
return new StateClass(newMarking, newDBM, allEnabled, readyEarliest);
|
|
1506
1606
|
}
|
|
1507
1607
|
function findEnabledTransitions(net, marking, environmentPlaces, environmentMode) {
|
|
1508
1608
|
const enabled = [];
|
|
@@ -1661,14 +1761,37 @@ function extractMarking(ctx, reachableApp, flatNet) {
|
|
|
1661
1761
|
}
|
|
1662
1762
|
|
|
1663
1763
|
// src/verification/z3/name-coloured-encoder.ts
|
|
1664
|
-
function
|
|
1665
|
-
const
|
|
1666
|
-
|
|
1667
|
-
|
|
1764
|
+
function colourSlotBound(coloured, semiflows) {
|
|
1765
|
+
const w = (inv, pid) => inv.weights[pid] ?? 0;
|
|
1766
|
+
const isSemiflow = (inv) => inv.weights.every((x) => x >= 0);
|
|
1767
|
+
let single = null;
|
|
1768
|
+
for (const inv of semiflows) {
|
|
1769
|
+
if (isSemiflow(inv) && inv.constant >= 1 && coloured.every((pid) => w(inv, pid) >= 1)) {
|
|
1770
|
+
if (single === null || inv.constant < single) single = inv.constant;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
if (single !== null) return single;
|
|
1774
|
+
let sumConst = 0;
|
|
1775
|
+
const covered = new Array(coloured.length).fill(false);
|
|
1776
|
+
for (const inv of semiflows) {
|
|
1777
|
+
if (!isSemiflow(inv)) continue;
|
|
1778
|
+
let touches = false;
|
|
1779
|
+
for (let i = 0; i < coloured.length; i++) {
|
|
1780
|
+
if (w(inv, coloured[i]) >= 1) {
|
|
1781
|
+
covered[i] = true;
|
|
1782
|
+
touches = true;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
if (touches) sumConst += inv.constant;
|
|
1668
1786
|
}
|
|
1787
|
+
if (covered.every((c) => c) && sumConst >= 1) return sumConst;
|
|
1788
|
+
return null;
|
|
1789
|
+
}
|
|
1790
|
+
function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrierPlaces, semiflows) {
|
|
1791
|
+
const P = flat.places.length;
|
|
1669
1792
|
const isColoured = new Array(P).fill(false);
|
|
1670
|
-
for (const
|
|
1671
|
-
const ms =
|
|
1793
|
+
for (const t of net.transitions) {
|
|
1794
|
+
const ms = t.matchSpec;
|
|
1672
1795
|
if (ms) {
|
|
1673
1796
|
for (const key of ms.keys) {
|
|
1674
1797
|
const pid = flat.placeIndex.get(key.place.name);
|
|
@@ -1677,21 +1800,25 @@ function buildColouredPlan(net, flat, initial, budgetNames) {
|
|
|
1677
1800
|
}
|
|
1678
1801
|
}
|
|
1679
1802
|
}
|
|
1803
|
+
if (fragmentMode === "extended") {
|
|
1804
|
+
for (const c of carrierPlaces) {
|
|
1805
|
+
const pid = flat.placeIndex.get(c);
|
|
1806
|
+
if (pid != null) isColoured[pid] = true;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1680
1809
|
const coloured = [];
|
|
1681
1810
|
for (let i = 0; i < P; i++) if (isColoured[i]) coloured.push(i);
|
|
1682
1811
|
if (coloured.length === 0) return null;
|
|
1683
1812
|
for (const pid of coloured) {
|
|
1684
1813
|
if (initial.tokens(flat.places[pid]) !== 0) return null;
|
|
1685
1814
|
}
|
|
1815
|
+
const k = colourSlotBound(coloured, semiflows);
|
|
1816
|
+
if (k === null) return null;
|
|
1686
1817
|
const budgetIdx = /* @__PURE__ */ new Set();
|
|
1687
1818
|
for (const n of budgetNames) {
|
|
1688
1819
|
const i = flat.placeIndex.get(n);
|
|
1689
1820
|
if (i != null) budgetIdx.add(i);
|
|
1690
1821
|
}
|
|
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
1822
|
for (const ft of flat.transitions) {
|
|
1696
1823
|
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
1824
|
if (touches) return null;
|
|
@@ -1705,38 +1832,21 @@ function buildColouredPlan(net, flat, initial, budgetNames) {
|
|
|
1705
1832
|
if (colouredOut.length !== 0 || colouredIn.length === 0) return null;
|
|
1706
1833
|
if (colouredIn.some((pid) => ft.preVector[pid] !== 1)) return null;
|
|
1707
1834
|
classes.push({ kind: "join", colouredIn });
|
|
1835
|
+
} else if (colouredIn.length !== 0) {
|
|
1836
|
+
if (fragmentMode !== "extended") return null;
|
|
1837
|
+
if (colouredIn.length !== 1 || ft.preVector[colouredIn[0]] !== 1) return null;
|
|
1838
|
+
if (colouredOut.some((o) => ft.postVector[o] !== 1)) return null;
|
|
1839
|
+
classes.push({ kind: "consume", inputCol: colouredIn[0], colouredOut });
|
|
1708
1840
|
} else if (colouredOut.length !== 0) {
|
|
1709
|
-
if (colouredIn.length !== 0) return null;
|
|
1710
1841
|
if (colouredOut.some((o) => ft.postVector[o] !== 1)) return null;
|
|
1711
1842
|
let budgetConsumed = 0;
|
|
1712
1843
|
for (const b of budgetIdx) budgetConsumed += ft.preVector[b];
|
|
1713
1844
|
if (budgetConsumed < 1) return null;
|
|
1714
1845
|
classes.push({ kind: "mint", colouredOut });
|
|
1715
1846
|
} else {
|
|
1716
|
-
if (colouredIn.length !== 0) return null;
|
|
1717
1847
|
classes.push({ kind: "untouched" });
|
|
1718
1848
|
}
|
|
1719
1849
|
}
|
|
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
1850
|
return { coloured, isColoured, k, classes };
|
|
1741
1851
|
}
|
|
1742
1852
|
function buildLayout(ctx, plan, P) {
|
|
@@ -1760,7 +1870,7 @@ function buildLayout(ctx, plan, P) {
|
|
|
1760
1870
|
}
|
|
1761
1871
|
return { colUnc, colCol, nCols, cur, nxt };
|
|
1762
1872
|
}
|
|
1763
|
-
function encodeColoured(ctx, fp, plan, flat, initial, property, invariants) {
|
|
1873
|
+
function encodeColoured(ctx, fp, plan, flat, initial, property, invariants, sinkPlaces = /* @__PURE__ */ new Set()) {
|
|
1764
1874
|
const P = flat.places.length;
|
|
1765
1875
|
const k = plan.k;
|
|
1766
1876
|
const lay = buildLayout(ctx, plan, P);
|
|
@@ -1807,7 +1917,7 @@ function encodeColoured(ctx, fp, plan, flat, initial, property, invariants) {
|
|
|
1807
1917
|
}
|
|
1808
1918
|
});
|
|
1809
1919
|
}
|
|
1810
|
-
} else {
|
|
1920
|
+
} else if (cls.kind === "join") {
|
|
1811
1921
|
const colouredIn = cls.colouredIn;
|
|
1812
1922
|
for (let c = 0; c < k; c++) {
|
|
1813
1923
|
const cc = c;
|
|
@@ -1820,9 +1930,27 @@ function encodeColoured(ctx, fp, plan, flat, initial, property, invariants) {
|
|
|
1820
1930
|
}
|
|
1821
1931
|
});
|
|
1822
1932
|
}
|
|
1933
|
+
} else {
|
|
1934
|
+
const inputCol = cls.inputCol;
|
|
1935
|
+
const colouredOut = cls.colouredOut;
|
|
1936
|
+
for (let c = 0; c < k; c++) {
|
|
1937
|
+
const cc = c;
|
|
1938
|
+
addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_consume_${cc}`, (enab, upd) => {
|
|
1939
|
+
uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
|
|
1940
|
+
const icol = lay.colCol[inputCol][cc];
|
|
1941
|
+
enab.push(lay.cur[icol].ge(1));
|
|
1942
|
+
upd.set(icol, lay.cur[icol].add(-1));
|
|
1943
|
+
for (const o of colouredOut) {
|
|
1944
|
+
const ocol = lay.colCol[o][cc];
|
|
1945
|
+
upd.set(ocol, lay.cur[ocol].add(1));
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1823
1949
|
}
|
|
1824
1950
|
}
|
|
1825
|
-
addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property)
|
|
1951
|
+
if (!addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property, sinkPlaces)) {
|
|
1952
|
+
return null;
|
|
1953
|
+
}
|
|
1826
1954
|
return {
|
|
1827
1955
|
errorExpr: error.call(),
|
|
1828
1956
|
reachableDecl: reachable
|
|
@@ -1886,20 +2014,22 @@ function liftedInvariant(ctx, inv, plan, lay, vars) {
|
|
|
1886
2014
|
}
|
|
1887
2015
|
return sum.eq(inv.constant);
|
|
1888
2016
|
}
|
|
1889
|
-
function addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property) {
|
|
2017
|
+
function addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property, sinkPlaces) {
|
|
2018
|
+
const violation = encodeViolation(ctx, plan, lay, flat, property, lay.cur, sinkPlaces);
|
|
2019
|
+
if (violation === null) return false;
|
|
1890
2020
|
const reachBody = reachable.call(...lay.cur);
|
|
1891
|
-
const violation = encodeViolation(ctx, plan, lay, flat, property, lay.cur);
|
|
1892
2021
|
const body = ctx.And(reachBody, violation);
|
|
1893
2022
|
const head = error.call();
|
|
1894
2023
|
const qRule = ctx.ForAll([...lay.cur], ctx.Implies(body, head));
|
|
1895
2024
|
fp.addRule(qRule, "error");
|
|
2025
|
+
return true;
|
|
1896
2026
|
}
|
|
1897
|
-
function encodeViolation(ctx, plan, lay, flat, property, cur) {
|
|
2027
|
+
function encodeViolation(ctx, plan, lay, flat, property, cur, sinkPlaces) {
|
|
1898
2028
|
switch (property.type) {
|
|
1899
2029
|
case "place-bound":
|
|
1900
2030
|
case "branch-place-bound": {
|
|
1901
2031
|
const idx = flatNetIndexOf(flat, property.place);
|
|
1902
|
-
if (idx < 0) return
|
|
2032
|
+
if (idx < 0) return null;
|
|
1903
2033
|
return aggregate(plan, lay, idx, cur).gt(property.bound);
|
|
1904
2034
|
}
|
|
1905
2035
|
case "mutual-exclusion": {
|
|
@@ -1917,15 +2047,130 @@ function encodeViolation(ctx, plan, lay, flat, property, cur) {
|
|
|
1917
2047
|
if (conds.length === 0) return ctx.Bool.val(false);
|
|
1918
2048
|
return conds.length === 1 ? conds[0] : ctx.And(...conds);
|
|
1919
2049
|
}
|
|
1920
|
-
// Quiescence properties are never routed here.
|
|
1921
2050
|
case "deadlock-free":
|
|
1922
|
-
|
|
2051
|
+
return encodeColouredDeadlock(ctx, plan, lay, flat, sinkPlaces);
|
|
2052
|
+
case "joined-or-dead-lettered": {
|
|
2053
|
+
const idx = flatNetIndexOf(flat, property.pending);
|
|
2054
|
+
if (idx < 0) return null;
|
|
2055
|
+
const deadlock = encodeColouredDeadlock(ctx, plan, lay, flat, sinkPlaces);
|
|
2056
|
+
return ctx.And(deadlock, aggregate(plan, lay, idx, cur).ge(1));
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
function andAll(ctx, xs) {
|
|
2061
|
+
if (xs.length === 0) return ctx.Bool.val(true);
|
|
2062
|
+
let r = xs[0];
|
|
2063
|
+
for (let i = 1; i < xs.length; i++) r = ctx.And(r, xs[i]);
|
|
2064
|
+
return r;
|
|
2065
|
+
}
|
|
2066
|
+
function orAll(ctx, xs) {
|
|
2067
|
+
if (xs.length === 0) return ctx.Bool.val(false);
|
|
2068
|
+
let r = xs[0];
|
|
2069
|
+
for (let i = 1; i < xs.length; i++) r = ctx.Or(r, xs[i]);
|
|
2070
|
+
return r;
|
|
2071
|
+
}
|
|
2072
|
+
function injectedEnvIndices2(flat) {
|
|
2073
|
+
const out = /* @__PURE__ */ new Map();
|
|
2074
|
+
for (const [name, bound] of flat.environmentInjection) {
|
|
2075
|
+
const idx = flat.placeIndex.get(name);
|
|
2076
|
+
if (idx != null) out.set(idx, bound);
|
|
2077
|
+
}
|
|
2078
|
+
return out;
|
|
2079
|
+
}
|
|
2080
|
+
function uncolouredDisable(ft, lay, plan, envInj) {
|
|
2081
|
+
const reasons = [];
|
|
2082
|
+
let permanentlyDisabled = false;
|
|
2083
|
+
const P = ft.preVector.length;
|
|
2084
|
+
for (let i = 0; i < P; i++) {
|
|
2085
|
+
if (plan.isColoured[i] || ft.preVector[i] === 0) continue;
|
|
2086
|
+
if (envInj.has(i)) {
|
|
2087
|
+
const bound = envInj.get(i);
|
|
2088
|
+
if (bound !== null && ft.preVector[i] > bound) permanentlyDisabled = true;
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
reasons.push(lay.cur[lay.colUnc[i]].lt(ft.preVector[i]));
|
|
2092
|
+
}
|
|
2093
|
+
for (const inh of ft.inhibitorPlaces) {
|
|
2094
|
+
reasons.push(lay.cur[lay.colUnc[inh]].gt(0));
|
|
2095
|
+
}
|
|
2096
|
+
for (const rd of ft.readPlaces) {
|
|
2097
|
+
if (envInj.has(rd)) {
|
|
2098
|
+
const bound = envInj.get(rd);
|
|
2099
|
+
if (bound !== null && bound < 1) permanentlyDisabled = true;
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
reasons.push(lay.cur[lay.colUnc[rd]].lt(1));
|
|
2103
|
+
}
|
|
2104
|
+
return { reasons, permanentlyDisabled };
|
|
2105
|
+
}
|
|
2106
|
+
function colouredDisabledTerm(ctx, cls, plan, lay) {
|
|
2107
|
+
const k = plan.k;
|
|
2108
|
+
switch (cls.kind) {
|
|
2109
|
+
case "untouched":
|
|
2110
|
+
return null;
|
|
2111
|
+
case "mint": {
|
|
2112
|
+
const perColour = [];
|
|
2113
|
+
for (let c = 0; c < k; c++) {
|
|
2114
|
+
const present = plan.coloured.map((q) => lay.cur[lay.colCol[q][c]].ge(1));
|
|
2115
|
+
perColour.push(orAll(ctx, present));
|
|
2116
|
+
}
|
|
2117
|
+
return andAll(ctx, perColour);
|
|
2118
|
+
}
|
|
2119
|
+
case "join": {
|
|
2120
|
+
const perColour = [];
|
|
2121
|
+
for (let c = 0; c < k; c++) {
|
|
2122
|
+
const missing = cls.colouredIn.map((i) => lay.cur[lay.colCol[i][c]].eq(0));
|
|
2123
|
+
perColour.push(orAll(ctx, missing));
|
|
2124
|
+
}
|
|
2125
|
+
return andAll(ctx, perColour);
|
|
2126
|
+
}
|
|
2127
|
+
case "consume": {
|
|
2128
|
+
const perColour = [];
|
|
2129
|
+
for (let c = 0; c < k; c++) {
|
|
2130
|
+
perColour.push(lay.cur[lay.colCol[cls.inputCol][c]].eq(0));
|
|
2131
|
+
}
|
|
2132
|
+
return andAll(ctx, perColour);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
function encodeColouredDeadlock(ctx, plan, lay, flat, sinkPlaces) {
|
|
2137
|
+
const envInj = injectedEnvIndices2(flat);
|
|
2138
|
+
const disabledConditions = [];
|
|
2139
|
+
for (let ti = 0; ti < plan.classes.length; ti++) {
|
|
2140
|
+
const cls = plan.classes[ti];
|
|
2141
|
+
const ft = flat.transitions[ti];
|
|
2142
|
+
const { reasons, permanentlyDisabled } = uncolouredDisable(ft, lay, plan, envInj);
|
|
2143
|
+
if (permanentlyDisabled) {
|
|
2144
|
+
disabledConditions.push(ctx.Bool.val(true));
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
const term = colouredDisabledTerm(ctx, cls, plan, lay);
|
|
2148
|
+
if (term !== null) reasons.push(term);
|
|
2149
|
+
if (reasons.length === 0) {
|
|
1923
2150
|
return ctx.Bool.val(false);
|
|
2151
|
+
}
|
|
2152
|
+
disabledConditions.push(reasons.length === 1 ? reasons[0] : orAll(ctx, reasons));
|
|
2153
|
+
}
|
|
2154
|
+
const sinkIndices = /* @__PURE__ */ new Set();
|
|
2155
|
+
for (const sink of sinkPlaces) {
|
|
2156
|
+
const idx = flatNetIndexOf(flat, sink);
|
|
2157
|
+
if (idx >= 0) sinkIndices.add(idx);
|
|
2158
|
+
}
|
|
2159
|
+
if (sinkIndices.size > 0) {
|
|
2160
|
+
const nonSink = [];
|
|
2161
|
+
for (let pid = 0; pid < flat.places.length; pid++) {
|
|
2162
|
+
if (sinkIndices.has(pid)) continue;
|
|
2163
|
+
nonSink.push(aggregate(plan, lay, pid, lay.cur).ge(1));
|
|
2164
|
+
}
|
|
2165
|
+
if (nonSink.length > 0) {
|
|
2166
|
+
disabledConditions.push(orAll(ctx, nonSink));
|
|
2167
|
+
}
|
|
1924
2168
|
}
|
|
2169
|
+
return andAll(ctx, disabledConditions);
|
|
1925
2170
|
}
|
|
1926
2171
|
|
|
1927
2172
|
// src/verification/analysis/name-fragment.ts
|
|
1928
|
-
function classify(net) {
|
|
2173
|
+
function classify(net, mode, carrierPlaces) {
|
|
1929
2174
|
const coloured = /* @__PURE__ */ new Set();
|
|
1930
2175
|
let anyMatch = false;
|
|
1931
2176
|
for (const t of net.transitions) {
|
|
@@ -1935,9 +2180,18 @@ function classify(net) {
|
|
|
1935
2180
|
}
|
|
1936
2181
|
}
|
|
1937
2182
|
if (!anyMatch || coloured.size === 0) return null;
|
|
2183
|
+
if (mode === "extended") {
|
|
2184
|
+
for (const c of carrierPlaces) coloured.add(c);
|
|
2185
|
+
}
|
|
2186
|
+
for (const t of net.transitions) {
|
|
2187
|
+
if (t.resets.some((r) => coloured.has(r.place.name)) || t.reads.some((r) => coloured.has(r.place.name)) || t.inhibitors.some((i) => coloured.has(i.place.name))) {
|
|
2188
|
+
return null;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
1938
2191
|
const roles = /* @__PURE__ */ new Map();
|
|
1939
2192
|
for (const t of net.transitions) {
|
|
1940
|
-
const
|
|
2193
|
+
const colouredInputs = t.inputSpecs.filter((s) => coloured.has(s.place.name));
|
|
2194
|
+
const consumesColoured = colouredInputs.length > 0;
|
|
1941
2195
|
let producesColoured = false;
|
|
1942
2196
|
if (t.outputSpec !== null) {
|
|
1943
2197
|
for (const branch of enumerateBranches(t.outputSpec)) {
|
|
@@ -1958,11 +2212,16 @@ function classify(net) {
|
|
|
1958
2212
|
}
|
|
1959
2213
|
colouredIn.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
1960
2214
|
role = { type: "join", colouredIn };
|
|
2215
|
+
} else if (consumesColoured) {
|
|
2216
|
+
if (mode === "base") return null;
|
|
2217
|
+
if (colouredInputs.length !== 1) return null;
|
|
2218
|
+
const spec = colouredInputs[0];
|
|
2219
|
+
const countOne = spec.type === "one" || spec.type === "exactly" && spec.count === 1;
|
|
2220
|
+
if (!countOne) return null;
|
|
2221
|
+
role = { type: "consume", colouredInput: spec.place.name };
|
|
1961
2222
|
} else if (producesColoured) {
|
|
1962
|
-
if (consumesColoured) return null;
|
|
1963
2223
|
role = { type: "mint" };
|
|
1964
2224
|
} else {
|
|
1965
|
-
if (consumesColoured) return null;
|
|
1966
2225
|
role = { type: "ordinary" };
|
|
1967
2226
|
}
|
|
1968
2227
|
roles.set(t.name, role);
|
|
@@ -2113,7 +2372,7 @@ var NameStateClassGraph = class _NameStateClassGraph {
|
|
|
2113
2372
|
markingOf(idx) {
|
|
2114
2373
|
return this.classes[idx].base.marking;
|
|
2115
2374
|
}
|
|
2116
|
-
static build(net, initialMarking, fragment, maxClasses, environmentPlaces, environmentMode) {
|
|
2375
|
+
static build(net, initialMarking, fragment, maxClasses, environmentPlaces, environmentMode, prioritySemantics = "none") {
|
|
2117
2376
|
const envMode = environmentMode ?? ignore();
|
|
2118
2377
|
const envPlaces = /* @__PURE__ */ new Set();
|
|
2119
2378
|
if (environmentPlaces) {
|
|
@@ -2133,7 +2392,20 @@ var NameStateClassGraph = class _NameStateClassGraph {
|
|
|
2133
2392
|
}
|
|
2134
2393
|
const curIdx = queue.shift();
|
|
2135
2394
|
const current = graph.classes[curIdx];
|
|
2136
|
-
|
|
2395
|
+
const enabled = current.base.enabledTransitions;
|
|
2396
|
+
for (let idxL = 0; idxL < enabled.length; idxL++) {
|
|
2397
|
+
const transition = enabled[idxL];
|
|
2398
|
+
if (prioritySemantics === "conflict" && priorityDominated(
|
|
2399
|
+
transition,
|
|
2400
|
+
idxL,
|
|
2401
|
+
enabled,
|
|
2402
|
+
current.base.readyEarliest,
|
|
2403
|
+
current.base.marking,
|
|
2404
|
+
current.names,
|
|
2405
|
+
fragment
|
|
2406
|
+
)) {
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2137
2409
|
const role = fragment.role(transition.name);
|
|
2138
2410
|
for (const vt of expandTransition(transition)) {
|
|
2139
2411
|
const baseSucc = computeSuccessor(net, current.base, vt, envPlaces, envMode);
|
|
@@ -2165,12 +2437,66 @@ var NameStateClassGraph = class _NameStateClassGraph {
|
|
|
2165
2437
|
this._successors[from].push(to);
|
|
2166
2438
|
}
|
|
2167
2439
|
};
|
|
2440
|
+
var READY_EPS = 1e-9;
|
|
2441
|
+
function priorityDominated(l, idxL, enabled, readyEarliest, marking, names, fragment) {
|
|
2442
|
+
return enabled.some(
|
|
2443
|
+
(h, idxH) => h !== l && h.priority > l.priority && readyEarliest[idxH] <= readyEarliest[idxL] + READY_EPS && willFire(h, names, fragment) && sharesConsumedInput(h, l, marking)
|
|
2444
|
+
);
|
|
2445
|
+
}
|
|
2446
|
+
function willFire(h, names, fragment) {
|
|
2447
|
+
const role = fragment.role(h.name);
|
|
2448
|
+
switch (role.type) {
|
|
2449
|
+
case "join":
|
|
2450
|
+
return enablingSymbols(names, role.colouredIn).length > 0;
|
|
2451
|
+
case "consume":
|
|
2452
|
+
return names.symbolsIn(role.colouredInput).length > 0;
|
|
2453
|
+
case "ordinary":
|
|
2454
|
+
case "mint":
|
|
2455
|
+
return true;
|
|
2456
|
+
default: {
|
|
2457
|
+
const _exhaustive = role;
|
|
2458
|
+
return _exhaustive;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
function sharesConsumedInput(h, l, marking) {
|
|
2463
|
+
const lIns = /* @__PURE__ */ new Set();
|
|
2464
|
+
for (const p of l.inputPlaces()) lIns.add(p.name);
|
|
2465
|
+
for (const p of h.inputPlaces()) {
|
|
2466
|
+
if (lIns.has(p.name) && marking.tokens(p) < consumedDemand(h, p.name) + consumedDemand(l, p.name)) {
|
|
2467
|
+
return true;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
return false;
|
|
2471
|
+
}
|
|
2472
|
+
function consumedDemand(t, placeName) {
|
|
2473
|
+
let demand = 0;
|
|
2474
|
+
for (const spec of t.inputSpecs) {
|
|
2475
|
+
if (spec.place.name === placeName) demand += inputRequiredCount2(spec);
|
|
2476
|
+
}
|
|
2477
|
+
return demand;
|
|
2478
|
+
}
|
|
2479
|
+
function inputRequiredCount2(spec) {
|
|
2480
|
+
switch (spec.type) {
|
|
2481
|
+
case "one":
|
|
2482
|
+
return 1;
|
|
2483
|
+
case "exactly":
|
|
2484
|
+
return spec.count;
|
|
2485
|
+
case "all":
|
|
2486
|
+
return 1;
|
|
2487
|
+
case "at-least":
|
|
2488
|
+
return spec.minimum;
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
function colouredOutputs(outputPlaces, fragment) {
|
|
2492
|
+
return [...outputPlaces].filter((p) => fragment.isColoured(p.name)).map((p) => p.name);
|
|
2493
|
+
}
|
|
2168
2494
|
function nameSuccessors(role, names, outputPlaces, fragment, sym) {
|
|
2169
2495
|
switch (role.type) {
|
|
2170
2496
|
case "ordinary":
|
|
2171
2497
|
return [names.copy()];
|
|
2172
2498
|
case "mint": {
|
|
2173
|
-
const colouredOut =
|
|
2499
|
+
const colouredOut = colouredOutputs(outputPlaces, fragment);
|
|
2174
2500
|
const nm = names.copy();
|
|
2175
2501
|
if (colouredOut.length > 0) {
|
|
2176
2502
|
const fresh = sym.next++;
|
|
@@ -2187,6 +2513,17 @@ function nameSuccessors(role, names, outputPlaces, fragment, sym) {
|
|
|
2187
2513
|
}
|
|
2188
2514
|
return result;
|
|
2189
2515
|
}
|
|
2516
|
+
case "consume": {
|
|
2517
|
+
const colouredOut = colouredOutputs(outputPlaces, fragment);
|
|
2518
|
+
const result = [];
|
|
2519
|
+
for (const s of names.symbolsIn(role.colouredInput)) {
|
|
2520
|
+
const nm = names.copy();
|
|
2521
|
+
nm.remove(role.colouredInput, s, 1);
|
|
2522
|
+
for (const p of colouredOut) nm.add(p, s, 1);
|
|
2523
|
+
result.push(nm);
|
|
2524
|
+
}
|
|
2525
|
+
return result;
|
|
2526
|
+
}
|
|
2190
2527
|
}
|
|
2191
2528
|
}
|
|
2192
2529
|
function enablingSymbols(names, colouredIn) {
|
|
@@ -2210,13 +2547,21 @@ function enablingSymbols(names, colouredIn) {
|
|
|
2210
2547
|
|
|
2211
2548
|
// src/verification/nu-scg-verifier.ts
|
|
2212
2549
|
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);
|
|
2550
|
+
function verifyViaNameScg(net, initial, property, sinkPlaces, environmentPlaces, environmentMode, maxClasses, fragmentMode, carrierPlaces, prioritySemantics) {
|
|
2551
|
+
const fragment = classify(net, fragmentMode, carrierPlaces);
|
|
2215
2552
|
if (fragment === null) return null;
|
|
2216
2553
|
for (const p of initial.placesWithTokens()) {
|
|
2217
2554
|
if (fragment.isColoured(p.name)) return null;
|
|
2218
2555
|
}
|
|
2219
|
-
const scg = NameStateClassGraph.build(
|
|
2556
|
+
const scg = NameStateClassGraph.build(
|
|
2557
|
+
net,
|
|
2558
|
+
initial,
|
|
2559
|
+
fragment,
|
|
2560
|
+
maxClasses,
|
|
2561
|
+
environmentPlaces,
|
|
2562
|
+
environmentMode,
|
|
2563
|
+
prioritySemantics
|
|
2564
|
+
);
|
|
2220
2565
|
if (!scg.isComplete()) {
|
|
2221
2566
|
return {
|
|
2222
2567
|
verdict: {
|
|
@@ -2322,6 +2667,9 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2322
2667
|
_environmentMode = alwaysAvailable();
|
|
2323
2668
|
_timeoutMs = 6e4;
|
|
2324
2669
|
_nuMaxClasses = 1e5;
|
|
2670
|
+
_fragmentMode = "base";
|
|
2671
|
+
_carrierPlaces = /* @__PURE__ */ new Set();
|
|
2672
|
+
_prioritySemantics = "none";
|
|
2325
2673
|
static forNet(net) {
|
|
2326
2674
|
return new _SmtVerifier(net);
|
|
2327
2675
|
}
|
|
@@ -2381,6 +2729,50 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2381
2729
|
this._nuMaxClasses = max;
|
|
2382
2730
|
return this;
|
|
2383
2731
|
}
|
|
2732
|
+
/**
|
|
2733
|
+
* Selects the ν-net coloured-place fragment for Route B (NU-051). `base`
|
|
2734
|
+
* (default) admits the shipped mint → matched-join fragment only; `extended`
|
|
2735
|
+
* additionally admits the opt-in coloured-consumer (drain/relay) role and the
|
|
2736
|
+
* declared {@link carrierPlaces}. When `extended` is requested but the net
|
|
2737
|
+
* falls outside the coloured-consumer fragment, Route B declines and a short
|
|
2738
|
+
* note is appended to the report before falling back to the sound
|
|
2739
|
+
* over-approximation.
|
|
2740
|
+
*/
|
|
2741
|
+
fragmentMode(mode) {
|
|
2742
|
+
this._fragmentMode = mode;
|
|
2743
|
+
return this;
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Declares ν-net *carrier* places (NU-051, EXTENDED only): intermediate places
|
|
2747
|
+
* that carry a fresh name from the minting fork onward to a ν-join input. Under
|
|
2748
|
+
* {@link fragmentMode} `extended` they are unioned into the coloured set so the
|
|
2749
|
+
* existing mint co-mints one fresh name into all of them; under `base` they are
|
|
2750
|
+
* ignored. Accumulating. Throws if a declared place is not in the net — a
|
|
2751
|
+
* mistyped carrier name would let two fork branches mint independent names, so
|
|
2752
|
+
* the join never becomes name-enabled and the verifier would otherwise report a
|
|
2753
|
+
* confident false deadlock; it must surface, never silently proceed.
|
|
2754
|
+
*/
|
|
2755
|
+
carrierPlaces(...places) {
|
|
2756
|
+
for (const p of places) {
|
|
2757
|
+
if (![...this.net.places].some((np) => np.name === p.name)) {
|
|
2758
|
+
throw new Error(`declared carrier place '${p.name}' not in the net`);
|
|
2759
|
+
}
|
|
2760
|
+
this._carrierPlaces.add(p.name);
|
|
2761
|
+
}
|
|
2762
|
+
return this;
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* Selects how the Route-B name-aware analyzer treats transition priority
|
|
2766
|
+
* (NU-052). Defaults to `'none'` (the priority-blind over-approximation).
|
|
2767
|
+
* `'conflict'` models the executor's conflict-only priority resolution, so a
|
|
2768
|
+
* lower-priority transition pre-empted by a conflicting, no-later-ready,
|
|
2769
|
+
* strictly-higher-priority one is not explored — removing spurious
|
|
2770
|
+
* dead-letter-drain stalls the eager, priority-ordered executor never produces.
|
|
2771
|
+
*/
|
|
2772
|
+
prioritySemantics(semantics) {
|
|
2773
|
+
this._prioritySemantics = semantics;
|
|
2774
|
+
return this;
|
|
2775
|
+
}
|
|
2384
2776
|
/**
|
|
2385
2777
|
* Runs the verification pipeline.
|
|
2386
2778
|
*/
|
|
@@ -2403,9 +2795,13 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2403
2795
|
this._sinkPlaces,
|
|
2404
2796
|
this._environmentPlaces,
|
|
2405
2797
|
this._environmentMode,
|
|
2406
|
-
this._nuMaxClasses
|
|
2798
|
+
this._nuMaxClasses,
|
|
2799
|
+
this._fragmentMode,
|
|
2800
|
+
this._carrierPlaces,
|
|
2801
|
+
this._prioritySemantics
|
|
2407
2802
|
);
|
|
2408
|
-
|
|
2803
|
+
const deferToRouteA = outcome !== null && outcome.verdict.type === "unknown" && !isReachabilitySafety(this._property) && nuBounded;
|
|
2804
|
+
if (outcome !== null && !deferToRouteA) {
|
|
2409
2805
|
report.push("=== \u03BD-net Route B: name-aware state-class graph (NU-050) ===");
|
|
2410
2806
|
report.push(` Name-partition state classes: ${outcome.classCount}`);
|
|
2411
2807
|
report.push(outcome.note);
|
|
@@ -2427,6 +2823,15 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2427
2823
|
structuralResult: "n/a (\u03BD name-partition SCG)"
|
|
2428
2824
|
}
|
|
2429
2825
|
);
|
|
2826
|
+
} else if (deferToRouteA) {
|
|
2827
|
+
report.push(
|
|
2828
|
+
"\u03BD-net Route B inconclusive (name-partition truncated); deferring to Route A coloured IC3/PDR (NU-053)."
|
|
2829
|
+
);
|
|
2830
|
+
}
|
|
2831
|
+
if (this._fragmentMode === "extended" && !deferToRouteA) {
|
|
2832
|
+
report.push(
|
|
2833
|
+
"\u03BD-net Route B (EXTENDED) declined: net outside coloured-consumer fragment (a coloured place consumed count != 1 or by multiple inputs, carries a reset/read/inhibitor arc, or a join re-mints a coloured place); verified via sound over-approximation instead."
|
|
2834
|
+
);
|
|
2430
2835
|
}
|
|
2431
2836
|
}
|
|
2432
2837
|
report.push("Phase 1: Flattening net...");
|
|
@@ -2471,6 +2876,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2471
2876
|
report.push("Phase 3: Computing P-invariants...");
|
|
2472
2877
|
const matrix = IncidenceMatrix.from(flatNet);
|
|
2473
2878
|
const invariants = computePInvariants(matrix, flatNet, this._initialMarking);
|
|
2879
|
+
const semiflows = computePSemiflows(matrix, flatNet, this._initialMarking);
|
|
2474
2880
|
report.push(` Found: ${invariants.length} P-invariant(s)`);
|
|
2475
2881
|
const structurallyBounded = isCoveredByInvariants(invariants, flatNet.places.length);
|
|
2476
2882
|
report.push(` Structurally bounded: ${structurallyBounded ? "YES" : "NO"}`);
|
|
@@ -2479,7 +2885,15 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2479
2885
|
}
|
|
2480
2886
|
report.push("");
|
|
2481
2887
|
report.push("Phase 4: IC3/PDR verification via Z3 Spacer...");
|
|
2482
|
-
const colouredPlan = hasMatch && nuBounded
|
|
2888
|
+
const colouredPlan = hasMatch && nuBounded ? buildColouredPlan(
|
|
2889
|
+
this.net,
|
|
2890
|
+
flatNet,
|
|
2891
|
+
this._initialMarking,
|
|
2892
|
+
this._budgetPlaces,
|
|
2893
|
+
this._fragmentMode,
|
|
2894
|
+
this._carrierPlaces,
|
|
2895
|
+
semiflows
|
|
2896
|
+
) : null;
|
|
2483
2897
|
let runner;
|
|
2484
2898
|
try {
|
|
2485
2899
|
runner = await createSpacerRunner(this._timeoutMs);
|
|
@@ -2505,7 +2919,23 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2505
2919
|
report.push(
|
|
2506
2920
|
` \u03BD-encoding: name-coloured (exact within budget k=${colouredPlan.k}; ${colouredPlan.coloured.length} coloured place(s))`
|
|
2507
2921
|
);
|
|
2508
|
-
encoding = encodeColoured(runner.ctx, runner.fp, colouredPlan, flatNet, this._initialMarking, this._property, invariants);
|
|
2922
|
+
encoding = encodeColoured(runner.ctx, runner.fp, colouredPlan, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
|
|
2923
|
+
if (encoding == null) {
|
|
2924
|
+
const reason = "property names a place that does not resolve in the net; refusing to certify (the encoding would be vacuously proven)";
|
|
2925
|
+
report.push(" Status: UNKNOWN (unresolved property place)\n");
|
|
2926
|
+
report.push("=== RESULT ===\n");
|
|
2927
|
+
report.push(`UNKNOWN: ${reason}`);
|
|
2928
|
+
return buildResult(
|
|
2929
|
+
{ type: "unknown", reason },
|
|
2930
|
+
report.join("\n"),
|
|
2931
|
+
invariants,
|
|
2932
|
+
[],
|
|
2933
|
+
[],
|
|
2934
|
+
[],
|
|
2935
|
+
performance.now() - start,
|
|
2936
|
+
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2509
2939
|
} else {
|
|
2510
2940
|
encoding = encode(runner.ctx, runner.fp, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
|
|
2511
2941
|
}
|
|
@@ -2655,6 +3085,10 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2655
3085
|
*/
|
|
2656
3086
|
applyNuGuard(result, hasMatch, nuBounded, exact) {
|
|
2657
3087
|
if (!hasMatch || result.verdict.type === "unknown") return result;
|
|
3088
|
+
if (exact) {
|
|
3089
|
+
const note2 = "\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 / NU-053).\n";
|
|
3090
|
+
return { ...result, report: result.report + note2 };
|
|
3091
|
+
}
|
|
2658
3092
|
if (!isReachabilitySafety(this._property)) {
|
|
2659
3093
|
return downgradeToUnknown(
|
|
2660
3094
|
result,
|
|
@@ -2667,7 +3101,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2667
3101
|
"\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
3102
|
);
|
|
2669
3103
|
}
|
|
2670
|
-
const note =
|
|
3104
|
+
const note = "\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
3105
|
return { ...result, report: result.report + note };
|
|
2672
3106
|
}
|
|
2673
3107
|
};
|
|
@@ -2753,6 +3187,7 @@ export {
|
|
|
2753
3187
|
pInvariant,
|
|
2754
3188
|
pInvariantToString,
|
|
2755
3189
|
computePInvariants,
|
|
3190
|
+
computePSemiflows,
|
|
2756
3191
|
isCoveredByInvariants,
|
|
2757
3192
|
structuralCheck,
|
|
2758
3193
|
findMinimalSiphons,
|
|
@@ -2770,4 +3205,4 @@ export {
|
|
|
2770
3205
|
isProven,
|
|
2771
3206
|
isViolated
|
|
2772
3207
|
};
|
|
2773
|
-
//# sourceMappingURL=chunk-
|
|
3208
|
+
//# sourceMappingURL=chunk-V3WTQRHC.js.map
|