libpetri 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -2
- package/dist/chunk-KO6TSB47.js +1016 -0
- package/dist/chunk-KO6TSB47.js.map +1 -0
- package/dist/{chunk-5W6SVYPD.js → chunk-WYCGGQAW.js} +1744 -737
- package/dist/chunk-WYCGGQAW.js.map +1 -0
- package/dist/debug/index.d.ts +2 -2
- package/dist/doclet/index.d.ts +12 -3
- package/dist/doclet/index.js +5 -1
- package/dist/doclet/index.js.map +1 -1
- package/dist/doclet/resources/petrinet-diagrams.css +21 -0
- package/dist/doclet/resources/petrinet-diagrams.js +6366 -6360
- package/dist/{render-ZGZEZ5RK.js → elk-place-YVNQFGXI.js} +3 -258
- package/dist/elk-place-YVNQFGXI.js.map +1 -0
- package/dist/{event-store-Df_sAVQ_.d.ts → event-store-2FOAeUyh.d.ts} +1 -1
- package/dist/export/index.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/pan-zoom-Cp51IkDl.d.ts +33 -0
- package/dist/{petri-net-UQBBkvLl.d.ts → petri-net-hduM6tJf.d.ts} +20 -1
- package/dist/preprocess-FN3F75JR.js +193 -0
- package/dist/preprocess-FN3F75JR.js.map +1 -0
- package/dist/render-QOHGDWNE.js +78 -0
- package/dist/render-QOHGDWNE.js.map +1 -0
- package/dist/render-dom/index.d.ts +28 -29
- package/dist/render-dom/index.js +14 -21
- package/dist/render-dom/index.js.map +1 -1
- package/dist/verification/index.d.ts +440 -71
- package/dist/verification/index.js +43 -5
- package/dist/verification/index.js.map +1 -1
- package/dist/viewer/index.d.ts +24 -32
- package/dist/viewer/index.js +9 -1003
- package/dist/viewer/index.js.map +1 -1
- package/dist/viewer/viewer.css +21 -0
- package/dist/viewer/viewer.iife.js +6366 -6360
- package/package.json +5 -8
- package/dist/chunk-5W6SVYPD.js.map +0 -1
- package/dist/render-ZGZEZ5RK.js.map +0 -1
|
@@ -409,7 +409,7 @@ function flatten(net, environmentPlaces = /* @__PURE__ */ new Set(), environment
|
|
|
409
409
|
for (const arc of t.reads) allPlacesSet.set(arc.place.name, arc.place);
|
|
410
410
|
for (const arc of t.resets) allPlacesSet.set(arc.place.name, arc.place);
|
|
411
411
|
}
|
|
412
|
-
const places = [...allPlacesSet.values()].sort((a, b) => a.name
|
|
412
|
+
const places = [...allPlacesSet.values()].sort((a, b) => compareCodePoints(a.name, b.name));
|
|
413
413
|
const placeIndex = /* @__PURE__ */ new Map();
|
|
414
414
|
for (let i = 0; i < places.length; i++) {
|
|
415
415
|
placeIndex.set(places[i].name, i);
|
|
@@ -497,6 +497,20 @@ function enumerateOutputBranches(t) {
|
|
|
497
497
|
}
|
|
498
498
|
return [/* @__PURE__ */ new Set()];
|
|
499
499
|
}
|
|
500
|
+
function compareCodePoints(a, b) {
|
|
501
|
+
const ia = a[Symbol.iterator]();
|
|
502
|
+
const ib = b[Symbol.iterator]();
|
|
503
|
+
for (; ; ) {
|
|
504
|
+
const na = ia.next();
|
|
505
|
+
const nb = ib.next();
|
|
506
|
+
if (na.done && nb.done) return 0;
|
|
507
|
+
if (na.done) return -1;
|
|
508
|
+
if (nb.done) return 1;
|
|
509
|
+
const ca = na.value.codePointAt(0);
|
|
510
|
+
const cb = nb.value.codePointAt(0);
|
|
511
|
+
if (ca !== cb) return ca - cb;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
500
514
|
|
|
501
515
|
// src/verification/encoding/incidence-matrix.ts
|
|
502
516
|
var IncidenceMatrix = class _IncidenceMatrix {
|
|
@@ -661,42 +675,21 @@ function computePInvariants(matrix, flatNet, initialMarking) {
|
|
|
661
675
|
}
|
|
662
676
|
}
|
|
663
677
|
if (!isZero) continue;
|
|
678
|
+
if (!rowIsExact(augmented[row], T, P)) {
|
|
679
|
+
invariants.push(rawInvariant(augmented[row], T, P, flatNet, initialMarking));
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
664
682
|
const weights = new Array(P);
|
|
665
|
-
let allNonNegative = true;
|
|
666
683
|
let hasPositive = false;
|
|
684
|
+
let hasNegative = false;
|
|
667
685
|
for (let i = 0; i < P; i++) {
|
|
668
686
|
weights[i] = augmented[row][T + i];
|
|
669
|
-
if (weights[i] < 0) {
|
|
670
|
-
allNonNegative = false;
|
|
671
|
-
break;
|
|
672
|
-
}
|
|
673
687
|
if (weights[i] > 0) hasPositive = true;
|
|
688
|
+
if (weights[i] < 0) hasNegative = true;
|
|
674
689
|
}
|
|
675
|
-
if (!
|
|
676
|
-
|
|
677
|
-
for (let i = 0; i < P; i++)
|
|
678
|
-
if (augmented[row][T + i] > 0) {
|
|
679
|
-
allNonPositive = false;
|
|
680
|
-
break;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
if (allNonPositive) {
|
|
684
|
-
for (let i = 0; i < P; i++) {
|
|
685
|
-
weights[i] = -augmented[row][T + i];
|
|
686
|
-
}
|
|
687
|
-
hasPositive = true;
|
|
688
|
-
allNonNegative = true;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
if (!allNonNegative || !hasPositive) continue;
|
|
692
|
-
let g = 0;
|
|
693
|
-
for (const w of weights) {
|
|
694
|
-
if (w > 0) g = gcd(g, w);
|
|
695
|
-
}
|
|
696
|
-
if (g > 1) {
|
|
697
|
-
for (let i = 0; i < P; i++) {
|
|
698
|
-
weights[i] = weights[i] / g;
|
|
699
|
-
}
|
|
690
|
+
if (!hasPositive && !hasNegative) continue;
|
|
691
|
+
if (!hasPositive) {
|
|
692
|
+
for (let i = 0; i < P; i++) weights[i] = -weights[i];
|
|
700
693
|
}
|
|
701
694
|
const support = /* @__PURE__ */ new Set();
|
|
702
695
|
let constant = 0;
|
|
@@ -711,6 +704,123 @@ function computePInvariants(matrix, flatNet, initialMarking) {
|
|
|
711
704
|
}
|
|
712
705
|
return invariants;
|
|
713
706
|
}
|
|
707
|
+
function rowIsExact(row, T, P) {
|
|
708
|
+
for (let i = 0; i < P; i++) {
|
|
709
|
+
if (!Number.isSafeInteger(row[T + i])) return false;
|
|
710
|
+
}
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
function rawInvariant(row, T, P, flatNet, initialMarking) {
|
|
714
|
+
const weights = new Array(P);
|
|
715
|
+
const support = /* @__PURE__ */ new Set();
|
|
716
|
+
let constant = 0;
|
|
717
|
+
for (let i = 0; i < P; i++) {
|
|
718
|
+
weights[i] = row[T + i];
|
|
719
|
+
if (weights[i] !== 0) {
|
|
720
|
+
support.add(i);
|
|
721
|
+
constant += weights[i] * initialMarking.tokens(flatNet.places[i]);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return pInvariant(weights, constant, support);
|
|
725
|
+
}
|
|
726
|
+
function validateInvariantsExact(matrix, invariants, flatNet, initialMarking) {
|
|
727
|
+
const nonlinear = nonlinearPlaces(flatNet);
|
|
728
|
+
const valid = [];
|
|
729
|
+
const dropped = [];
|
|
730
|
+
for (const inv of invariants) {
|
|
731
|
+
const reason = exactCheckFailure(matrix, inv, nonlinear, flatNet, initialMarking);
|
|
732
|
+
if (reason === null) {
|
|
733
|
+
valid.push(inv);
|
|
734
|
+
} else {
|
|
735
|
+
dropped.push({ invariant: inv, reason });
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return { valid, dropped };
|
|
739
|
+
}
|
|
740
|
+
function nonlinearPlaces(flatNet) {
|
|
741
|
+
const nonlinear = /* @__PURE__ */ new Set();
|
|
742
|
+
for (const ft of flatNet.transitions) {
|
|
743
|
+
for (let p = 0; p < ft.consumeAll.length; p++) {
|
|
744
|
+
if (ft.consumeAll[p]) nonlinear.add(p);
|
|
745
|
+
}
|
|
746
|
+
for (const p of ft.resetPlaces) nonlinear.add(p);
|
|
747
|
+
}
|
|
748
|
+
return nonlinear;
|
|
749
|
+
}
|
|
750
|
+
function exactCheckFailure(matrix, inv, nonlinear, flatNet, initialMarking) {
|
|
751
|
+
const P = matrix.numPlaces();
|
|
752
|
+
const T = matrix.numTransitions();
|
|
753
|
+
if (inv.weights.length !== P) {
|
|
754
|
+
return `weight vector has ${inv.weights.length} entries, expected ${P}`;
|
|
755
|
+
}
|
|
756
|
+
for (let p = 0; p < P; p++) {
|
|
757
|
+
if (!Number.isSafeInteger(inv.weights[p])) {
|
|
758
|
+
return `weight overflow at place '${placeName(flatNet, p)}' (exact value outside this implementation's integer extraction range)`;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (!Number.isSafeInteger(inv.constant)) {
|
|
762
|
+
return `constant ${inv.constant} is outside the safe-integer range`;
|
|
763
|
+
}
|
|
764
|
+
for (let p = 0; p < inv.weights.length; p++) {
|
|
765
|
+
if (inv.weights[p] !== 0 && nonlinear.has(p)) {
|
|
766
|
+
return `support intersects consume-all/reset place '${placeName(flatNet, p)}' (non-linear consumption; see Strengthening.lean H1)`;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const y = inv.weights.map((w) => BigInt(w));
|
|
770
|
+
const incidence = matrix.incidence();
|
|
771
|
+
for (let t = 0; t < T; t++) {
|
|
772
|
+
const row = incidence[t];
|
|
773
|
+
let dot = 0n;
|
|
774
|
+
for (let p = 0; p < P; p++) {
|
|
775
|
+
if (y[p] === 0n) continue;
|
|
776
|
+
if (!Number.isSafeInteger(row[p])) {
|
|
777
|
+
return `incidence entry ${row[p]} at [t=${t}][p=${p}] is outside the safe-integer range`;
|
|
778
|
+
}
|
|
779
|
+
dot += y[p] * BigInt(row[p]);
|
|
780
|
+
}
|
|
781
|
+
if (dot !== 0n) {
|
|
782
|
+
return `y*C is ${dot} (not 0) at ${columnName(flatNet, t)}`;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
let exact = 0n;
|
|
786
|
+
for (let p = 0; p < P; p++) {
|
|
787
|
+
if (y[p] === 0n) continue;
|
|
788
|
+
const tokens = initialMarking.tokens(flatNet.places[p]);
|
|
789
|
+
if (!Number.isSafeInteger(tokens)) {
|
|
790
|
+
return `initial marking of place ${p} (${tokens}) is outside the safe-integer range`;
|
|
791
|
+
}
|
|
792
|
+
exact += y[p] * BigInt(tokens);
|
|
793
|
+
}
|
|
794
|
+
if (exact !== BigInt(inv.constant)) {
|
|
795
|
+
return `constant ${inv.constant} does not match exact y*M0 = ${exact}`;
|
|
796
|
+
}
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
function placeName(flatNet, p) {
|
|
800
|
+
return flatNet.places[p]?.name ?? `#${p}`;
|
|
801
|
+
}
|
|
802
|
+
function columnName(flatNet, t) {
|
|
803
|
+
const ft = flatNet.transitions[t];
|
|
804
|
+
return ft != null ? `transition '${ft.name}'` : `env-injector column ${t - flatNet.transitions.length}`;
|
|
805
|
+
}
|
|
806
|
+
function sameInvariant(a, b) {
|
|
807
|
+
if (a.constant !== b.constant || a.weights.length !== b.weights.length) return false;
|
|
808
|
+
for (let i = 0; i < a.weights.length; i++) {
|
|
809
|
+
if (a.weights[i] !== b.weights[i]) return false;
|
|
810
|
+
}
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
function strengthenWithSemiflows(invariants, semiflows) {
|
|
814
|
+
const strengthened = [...invariants];
|
|
815
|
+
let added = 0;
|
|
816
|
+
for (const sf of semiflows) {
|
|
817
|
+
if (!strengthened.some((inv) => sameInvariant(inv, sf))) {
|
|
818
|
+
strengthened.push(sf);
|
|
819
|
+
added++;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return { invariants: strengthened, added };
|
|
823
|
+
}
|
|
714
824
|
function computePSemiflows(matrix, flatNet, initialMarking) {
|
|
715
825
|
const np = matrix.numPlaces();
|
|
716
826
|
const nt = matrix.numTransitions();
|
|
@@ -798,6 +908,7 @@ function keepSupportMinimal(rows) {
|
|
|
798
908
|
function isCoveredByInvariants(invariants, numPlaces) {
|
|
799
909
|
const covered = new Array(numPlaces).fill(false);
|
|
800
910
|
for (const inv of invariants) {
|
|
911
|
+
if (inv.weights.some((w) => w < 0)) continue;
|
|
801
912
|
for (const idx of inv.support) {
|
|
802
913
|
if (idx < numPlaces) covered[idx] = true;
|
|
803
914
|
}
|
|
@@ -825,6 +936,19 @@ function gcd(a, b) {
|
|
|
825
936
|
}
|
|
826
937
|
return a;
|
|
827
938
|
}
|
|
939
|
+
function canonicalInvariantOrder(invariants) {
|
|
940
|
+
const lex = (a, b) => {
|
|
941
|
+
const n = Math.min(a.length, b.length);
|
|
942
|
+
for (let i = 0; i < n; i++) {
|
|
943
|
+
if (a[i] !== b[i]) return a[i] - b[i];
|
|
944
|
+
}
|
|
945
|
+
return a.length - b.length;
|
|
946
|
+
};
|
|
947
|
+
const support = (inv) => [...inv.support].sort((x, y) => x - y);
|
|
948
|
+
return [...invariants].sort(
|
|
949
|
+
(a, b) => lex(support(a), support(b)) || lex(a.weights, b.weights) || a.constant - b.constant
|
|
950
|
+
);
|
|
951
|
+
}
|
|
828
952
|
|
|
829
953
|
// src/verification/invariant/structural-check.ts
|
|
830
954
|
var MAX_PLACES_FOR_SIPHON_ANALYSIS = 50;
|
|
@@ -987,315 +1111,675 @@ function isSubsetOf(sub, sup) {
|
|
|
987
1111
|
return true;
|
|
988
1112
|
}
|
|
989
1113
|
|
|
990
|
-
// src/verification/z3/
|
|
991
|
-
import {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1114
|
+
// src/verification/z3/z3-process.ts
|
|
1115
|
+
import { spawn, spawnSync } from "child_process";
|
|
1116
|
+
import { existsSync, mkdirSync, statSync, writeFileSync } from "fs";
|
|
1117
|
+
import * as path from "path";
|
|
1118
|
+
|
|
1119
|
+
// src/verification/z3/smt-text.ts
|
|
1120
|
+
function classifyFirstLine(stdout) {
|
|
1121
|
+
for (const raw of stdout.split("\n")) {
|
|
1122
|
+
const line = raw.trim();
|
|
1123
|
+
if (line === "sat" || line === "unsat" || line === "unknown") return line;
|
|
1124
|
+
}
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
function timeoutLine(stdout) {
|
|
1128
|
+
return stdout.split("\n").some((l) => l.trim() === "timeout");
|
|
1129
|
+
}
|
|
1130
|
+
function errorLine(text) {
|
|
1131
|
+
for (const raw of text.split("\n")) {
|
|
1132
|
+
const line = raw.trim();
|
|
1133
|
+
if (line.startsWith("(error")) return line;
|
|
1134
|
+
}
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
function sexprEnd(s, start) {
|
|
1138
|
+
let depth = 0;
|
|
1139
|
+
let inString = false;
|
|
1140
|
+
let inSymbol = false;
|
|
1141
|
+
for (let i = start; i < s.length; i++) {
|
|
1142
|
+
const c = s[i];
|
|
1143
|
+
if (inString) {
|
|
1144
|
+
if (c === '"') inString = false;
|
|
1145
|
+
} else if (inSymbol) {
|
|
1146
|
+
if (c === "|") inSymbol = false;
|
|
1147
|
+
} else if (c === '"') {
|
|
1148
|
+
inString = true;
|
|
1149
|
+
} else if (c === "|") {
|
|
1150
|
+
inSymbol = true;
|
|
1151
|
+
} else if (c === "(") {
|
|
1152
|
+
depth++;
|
|
1153
|
+
} else if (c === ")") {
|
|
1154
|
+
depth--;
|
|
1155
|
+
if (depth === 0) return i + 1;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return -1;
|
|
1159
|
+
}
|
|
1160
|
+
function extractDefineFuns(output) {
|
|
1161
|
+
const defs = [];
|
|
1162
|
+
let from = 0;
|
|
1163
|
+
for (; ; ) {
|
|
1164
|
+
const pos = output.indexOf("(define-fun", from);
|
|
1165
|
+
if (pos < 0) break;
|
|
1166
|
+
const end = sexprEnd(output, pos);
|
|
1167
|
+
if (end < 0) break;
|
|
1168
|
+
defs.push(output.slice(pos, end));
|
|
1169
|
+
from = end;
|
|
1170
|
+
}
|
|
1171
|
+
return defs;
|
|
1172
|
+
}
|
|
1173
|
+
function extractInvariant(output) {
|
|
1174
|
+
const defs = extractDefineFuns(output);
|
|
1175
|
+
return defs.length === 0 ? null : defs.join("\n");
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// src/verification/z3/z3-process.ts
|
|
1179
|
+
var Z3_ENV = "LIBPETRI_Z3";
|
|
1180
|
+
var DUMP_ENV = "LIBPETRI_SMT_DUMP";
|
|
1181
|
+
var GRACE_MS = 1e3;
|
|
1182
|
+
var VERSION_PROBE_MS = 5e3;
|
|
1183
|
+
var MIN_Z3_VERSION = { major: 4, minor: 8, patch: 0 };
|
|
1184
|
+
function parseZ3Version(text) {
|
|
1185
|
+
const m = /Z3 version (\d+)\.(\d+)(?:\.(\d+))?/.exec(text);
|
|
1186
|
+
if (m == null) return null;
|
|
1187
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: m[3] == null ? 0 : Number(m[3]) };
|
|
1188
|
+
}
|
|
1189
|
+
function formatZ3Version(v) {
|
|
1190
|
+
return `${v.major}.${v.minor}.${v.patch}`;
|
|
1191
|
+
}
|
|
1192
|
+
function compareZ3Version(a, b) {
|
|
1193
|
+
return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
|
|
1194
|
+
}
|
|
1195
|
+
var Z3Unavailable = class extends Error {
|
|
1196
|
+
constructor(message) {
|
|
1197
|
+
super(message);
|
|
1198
|
+
this.name = "Z3Unavailable";
|
|
1039
1199
|
}
|
|
1040
|
-
|
|
1200
|
+
};
|
|
1201
|
+
var Z3ProcessError = class extends Error {
|
|
1202
|
+
constructor(message) {
|
|
1203
|
+
super(message);
|
|
1204
|
+
this.name = "Z3ProcessError";
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
function replySucceeded(reply) {
|
|
1208
|
+
return reply.exit.kind === "exited" && reply.exit.code === 0;
|
|
1209
|
+
}
|
|
1210
|
+
function argsFor(timeoutMs) {
|
|
1211
|
+
return ["-smt2", "-in", `-t:${timeoutMs}`, `-T:${hardTimeoutSecs(timeoutMs)}`];
|
|
1212
|
+
}
|
|
1213
|
+
function hardTimeoutSecs(timeoutMs) {
|
|
1214
|
+
return Math.max(1, Math.ceil((timeoutMs + GRACE_MS) / 1e3));
|
|
1215
|
+
}
|
|
1216
|
+
function watchdogMs(timeoutMs) {
|
|
1217
|
+
return timeoutMs + 2 * GRACE_MS;
|
|
1218
|
+
}
|
|
1219
|
+
function timeoutBudget(timeoutMs) {
|
|
1220
|
+
return Math.max(1, Math.floor(Number.isFinite(timeoutMs) ? timeoutMs : 1));
|
|
1221
|
+
}
|
|
1222
|
+
function failureReason(reply, timeoutMs) {
|
|
1223
|
+
if (timeoutLine(reply.stdout)) {
|
|
1224
|
+
return `z3 hard timeout after ${hardTimeoutSecs(timeoutMs)}s`;
|
|
1225
|
+
}
|
|
1226
|
+
if (reply.exit.kind === "killed") {
|
|
1227
|
+
return `z3 did not exit within ${watchdogMs(timeoutMs)} ms and was killed`;
|
|
1228
|
+
}
|
|
1229
|
+
const err = errorLine(reply.stdout) ?? errorLine(reply.stderr);
|
|
1230
|
+
if (err != null) return `Z3 error: ${err}`;
|
|
1231
|
+
const stderr = reply.stderr.trim();
|
|
1232
|
+
if (stderr !== "") return `Z3 error: ${stderr}`;
|
|
1233
|
+
return `Unexpected Z3 output: ${reply.stdout.trim()}`;
|
|
1234
|
+
}
|
|
1235
|
+
function locateZ3(program, env = process.env) {
|
|
1236
|
+
const isFile = (p) => {
|
|
1041
1237
|
try {
|
|
1042
|
-
|
|
1238
|
+
return existsSync(p) && statSync(p).isFile();
|
|
1043
1239
|
} catch {
|
|
1240
|
+
return false;
|
|
1044
1241
|
}
|
|
1045
|
-
}
|
|
1046
|
-
return {
|
|
1047
|
-
ctx,
|
|
1048
|
-
fp,
|
|
1049
|
-
query,
|
|
1050
|
-
dispose
|
|
1051
1242
|
};
|
|
1243
|
+
if (program.includes("/") || program.includes(path.sep) || path.isAbsolute(program)) {
|
|
1244
|
+
return isFile(program) ? program : null;
|
|
1245
|
+
}
|
|
1246
|
+
const searchPath = env["PATH"] ?? "";
|
|
1247
|
+
const windows = process.platform === "win32";
|
|
1248
|
+
for (const dir of searchPath.split(path.delimiter)) {
|
|
1249
|
+
if (dir === "") continue;
|
|
1250
|
+
const candidate2 = path.join(dir, program);
|
|
1251
|
+
if (isFile(candidate2)) return candidate2;
|
|
1252
|
+
if (windows && isFile(candidate2 + ".exe")) return candidate2 + ".exe";
|
|
1253
|
+
}
|
|
1254
|
+
return null;
|
|
1052
1255
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1256
|
+
function z3SolverAt(program, env = process.env) {
|
|
1257
|
+
const located = locateZ3(program, env);
|
|
1258
|
+
if (located == null) {
|
|
1259
|
+
throw new Z3Unavailable(
|
|
1260
|
+
`z3 binary not found: ${program}; install z3 >= ${formatZ3Version(MIN_Z3_VERSION)} or set ${Z3_ENV}`
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
const probe = spawnSync(located, ["--version"], {
|
|
1264
|
+
encoding: "utf8",
|
|
1265
|
+
timeout: VERSION_PROBE_MS,
|
|
1266
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1267
|
+
});
|
|
1268
|
+
if (probe.error != null) {
|
|
1269
|
+
if (probe.error.code === "ETIMEDOUT") {
|
|
1270
|
+
throw new Z3Unavailable(`${program} --version did not answer within ${VERSION_PROBE_MS} ms`);
|
|
1271
|
+
}
|
|
1272
|
+
throw new Z3Unavailable(`failed to spawn ${program}: ${probe.error.message}`);
|
|
1273
|
+
}
|
|
1274
|
+
const version = parseZ3Version(probe.stdout ?? "");
|
|
1275
|
+
if (version == null) {
|
|
1276
|
+
const line = `${probe.stdout ?? ""}
|
|
1277
|
+
${probe.stderr ?? ""}`.split("\n").map((l) => l.trim()).find((l) => l !== "") ?? "";
|
|
1278
|
+
throw new Z3Unavailable(`z3 --version did not report a version: ${line}`);
|
|
1279
|
+
}
|
|
1280
|
+
if (compareZ3Version(version, MIN_Z3_VERSION) < 0) {
|
|
1281
|
+
throw new Z3Unavailable(
|
|
1282
|
+
`z3 ${formatZ3Version(version)} is older than the minimum ${formatZ3Version(MIN_Z3_VERSION)}`
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
return { program: located, version, dumpDir: null };
|
|
1057
1286
|
}
|
|
1058
|
-
function
|
|
1059
|
-
|
|
1287
|
+
function resolveZ3(env = process.env) {
|
|
1288
|
+
const configured = env[Z3_ENV];
|
|
1289
|
+
const program = configured == null || configured.trim() === "" ? "z3" : configured;
|
|
1290
|
+
const dump = env[DUMP_ENV];
|
|
1291
|
+
const solver = z3SolverAt(program, env);
|
|
1292
|
+
return { ...solver, dumpDir: dump == null || dump.trim() === "" ? null : dump };
|
|
1060
1293
|
}
|
|
1061
|
-
function
|
|
1062
|
-
|
|
1294
|
+
function z3Available(env = process.env) {
|
|
1295
|
+
try {
|
|
1296
|
+
resolveZ3(env);
|
|
1297
|
+
return true;
|
|
1298
|
+
} catch {
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
var dumpCounter = 0;
|
|
1303
|
+
function dumpSlot(solver, phase, script2) {
|
|
1304
|
+
if (solver.dumpDir == null) return null;
|
|
1305
|
+
dumpCounter += 1;
|
|
1306
|
+
try {
|
|
1307
|
+
mkdirSync(solver.dumpDir, { recursive: true });
|
|
1308
|
+
const base = path.join(solver.dumpDir, `${String(dumpCounter).padStart(3, "0")}-${phase}`);
|
|
1309
|
+
writeFileSync(`${base}.smt2`, script2);
|
|
1310
|
+
return base;
|
|
1311
|
+
} catch {
|
|
1312
|
+
return null;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
function dumpWrite(file, text) {
|
|
1316
|
+
try {
|
|
1317
|
+
writeFileSync(file, text);
|
|
1318
|
+
} catch {
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
function runZ3Text(solver, script2, phase, timeoutMs, extraArgs = []) {
|
|
1322
|
+
const budget = timeoutBudget(timeoutMs);
|
|
1323
|
+
const base = dumpSlot(solver, phase, script2);
|
|
1324
|
+
return new Promise((resolve, reject) => {
|
|
1325
|
+
const child = spawn(solver.program, [...argsFor(budget), ...extraArgs], {
|
|
1326
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1327
|
+
});
|
|
1328
|
+
const out = [];
|
|
1329
|
+
const err = [];
|
|
1330
|
+
let killed = false;
|
|
1331
|
+
let settled = false;
|
|
1332
|
+
child.stdout.on("data", (chunk) => out.push(chunk));
|
|
1333
|
+
child.stderr.on("data", (chunk) => err.push(chunk));
|
|
1334
|
+
child.stdin.on("error", () => {
|
|
1335
|
+
});
|
|
1336
|
+
const watchdog = setTimeout(() => {
|
|
1337
|
+
killed = true;
|
|
1338
|
+
child.kill("SIGKILL");
|
|
1339
|
+
}, watchdogMs(budget));
|
|
1340
|
+
child.on("error", (e) => {
|
|
1341
|
+
if (settled) return;
|
|
1342
|
+
settled = true;
|
|
1343
|
+
clearTimeout(watchdog);
|
|
1344
|
+
reject(new Z3ProcessError(`failed to spawn ${solver.program}: ${e.message}`));
|
|
1345
|
+
});
|
|
1346
|
+
child.on("close", (code) => {
|
|
1347
|
+
if (settled) return;
|
|
1348
|
+
settled = true;
|
|
1349
|
+
clearTimeout(watchdog);
|
|
1350
|
+
const reply = {
|
|
1351
|
+
stdout: Buffer.concat(out).toString("utf8"),
|
|
1352
|
+
stderr: Buffer.concat(err).toString("utf8"),
|
|
1353
|
+
exit: killed ? { kind: "killed" } : { kind: "exited", code }
|
|
1354
|
+
};
|
|
1355
|
+
if (base != null) {
|
|
1356
|
+
dumpWrite(`${base}.out`, reply.stdout);
|
|
1357
|
+
if (reply.stderr.trim() !== "") dumpWrite(`${base}.err`, reply.stderr);
|
|
1358
|
+
}
|
|
1359
|
+
resolve(reply);
|
|
1360
|
+
});
|
|
1361
|
+
child.stdin.end(script2);
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/verification/z3/spacer-runner.ts
|
|
1366
|
+
async function runZ3Spacer(solver, timeoutMs, smt2, phase) {
|
|
1367
|
+
let reply;
|
|
1368
|
+
try {
|
|
1369
|
+
reply = await runZ3Text(solver, smt2, phase, timeoutMs, ["fp.engine=spacer"]);
|
|
1370
|
+
} catch (e) {
|
|
1371
|
+
return { type: "unknown", reason: String(e?.message ?? e) };
|
|
1372
|
+
}
|
|
1373
|
+
const stdout = reply.stdout.trim();
|
|
1374
|
+
switch (classifyFirstLine(stdout)) {
|
|
1375
|
+
// unsat => no inductive invariant excludes the bad state => VIOLATED.
|
|
1376
|
+
case "unsat":
|
|
1377
|
+
return { type: "violated", answer: stdout };
|
|
1378
|
+
// sat => an inductive invariant exists => PROVEN.
|
|
1379
|
+
case "sat":
|
|
1380
|
+
return { type: "proven", invariantFormula: extractInvariant(stdout) };
|
|
1381
|
+
case "unknown":
|
|
1382
|
+
return { type: "unknown", reason: "Z3 answered unknown" };
|
|
1383
|
+
default:
|
|
1384
|
+
return { type: "unknown", reason: failureReason(reply, timeoutBudget(timeoutMs)) };
|
|
1385
|
+
}
|
|
1063
1386
|
}
|
|
1064
1387
|
|
|
1065
1388
|
// src/verification/z3/smt-encoder.ts
|
|
1066
|
-
function encode(
|
|
1389
|
+
function encode(flatNet, initialMarking, property, invariants, sinkPlaces = /* @__PURE__ */ new Set(), produceProofs = false) {
|
|
1067
1390
|
const P = flatNet.places.length;
|
|
1068
|
-
const
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
}
|
|
1391
|
+
const lines = [];
|
|
1392
|
+
const envInject = resolveEnvInjection(flatNet);
|
|
1393
|
+
if (produceProofs) lines.push("(set-option :produce-proofs true)");
|
|
1394
|
+
lines.push("(set-logic HORN)");
|
|
1395
|
+
lines.push("");
|
|
1396
|
+
lines.push(`(declare-fun Reachable (${ints(P).join(" ")}) Bool)`);
|
|
1397
|
+
lines.push("(declare-fun Error () Bool)");
|
|
1398
|
+
lines.push("");
|
|
1399
|
+
const mVars = vars(P, "");
|
|
1400
|
+
const mpVars = vars(P, "p");
|
|
1401
|
+
const m0 = [];
|
|
1402
|
+
for (let i = 0; i < P; i++) m0.push(String(initialMarking.tokens(flatNet.places[i])));
|
|
1403
|
+
lines.push(`(assert (Reachable ${m0.join(" ")}))`);
|
|
1404
|
+
lines.push("");
|
|
1405
|
+
for (const ft of flatNet.transitions) {
|
|
1406
|
+
lines.push(encodeTransitionRule(flatNet, ft, mVars, mpVars, invariants));
|
|
1407
|
+
}
|
|
1408
|
+
for (const inj of envInject) {
|
|
1409
|
+
lines.push(encodeInjectionRule(P, inj.pid, inj.bound, mVars, mpVars));
|
|
1410
|
+
}
|
|
1411
|
+
lines.push("");
|
|
1412
|
+
lines.push(encodeErrorRule(flatNet, property, mVars, sinkPlaces, envInject));
|
|
1413
|
+
lines.push("");
|
|
1414
|
+
lines.push("(assert (not Error))");
|
|
1415
|
+
lines.push("(check-sat)");
|
|
1416
|
+
if (produceProofs) lines.push("(get-proof)");
|
|
1417
|
+
lines.push("(get-model)");
|
|
1418
|
+
return { smt2: lines.join("\n"), placeCount: P };
|
|
1419
|
+
}
|
|
1420
|
+
function resolveEnvInjection(flatNet) {
|
|
1421
|
+
const out = [];
|
|
1088
1422
|
for (const [name, bound] of flatNet.environmentInjection) {
|
|
1089
|
-
const
|
|
1090
|
-
if (
|
|
1091
|
-
encodeInjectionRule(ctx, fp, reachable, idx, bound, P);
|
|
1423
|
+
const pid = flatNet.placeIndex.get(name);
|
|
1424
|
+
if (pid != null) out.push({ pid, bound });
|
|
1092
1425
|
}
|
|
1093
|
-
|
|
1094
|
-
return
|
|
1095
|
-
errorExpr: error.call(),
|
|
1096
|
-
reachableDecl: reachable
|
|
1097
|
-
};
|
|
1426
|
+
out.sort((a, b) => a.pid - b.pid);
|
|
1427
|
+
return out;
|
|
1098
1428
|
}
|
|
1099
|
-
function
|
|
1100
|
-
const
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
mVars.push(Int.const(`m${i}`));
|
|
1105
|
-
mPrimeVars.push(Int.const(`mp${i}`));
|
|
1106
|
-
}
|
|
1107
|
-
const reachBody = reachable.call(...mVars);
|
|
1108
|
-
const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
|
|
1109
|
-
const fireRelation = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
|
|
1110
|
-
let nonNeg = ctx.Bool.val(true);
|
|
1111
|
-
for (let i = 0; i < P; i++) {
|
|
1112
|
-
nonNeg = ctx.And(nonNeg, mPrimeVars[i].ge(0));
|
|
1113
|
-
}
|
|
1114
|
-
const invConstraints = encodeInvariantConstraints(ctx, invariants, mPrimeVars, P);
|
|
1115
|
-
let envBounds = ctx.Bool.val(true);
|
|
1116
|
-
for (const [name, bound] of flatNet.environmentBounds) {
|
|
1117
|
-
const idx = flatNet.placeIndex.get(name);
|
|
1118
|
-
if (idx != null) {
|
|
1119
|
-
envBounds = ctx.And(envBounds, mPrimeVars[idx].le(bound));
|
|
1120
|
-
}
|
|
1429
|
+
function envBounds(flatNet) {
|
|
1430
|
+
const out = [];
|
|
1431
|
+
for (const [name, max] of flatNet.environmentBounds) {
|
|
1432
|
+
const pid = flatNet.placeIndex.get(name);
|
|
1433
|
+
if (pid != null) out.push([pid, max]);
|
|
1121
1434
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
const allVars = [...mVars, ...mPrimeVars];
|
|
1125
|
-
const rule = ctx.Implies(body, head);
|
|
1126
|
-
const qRule = ctx.ForAll(allVars, rule);
|
|
1127
|
-
fp.addRule(qRule, `t_${ft.name}`);
|
|
1435
|
+
out.sort((a, b) => a[0] - b[0]);
|
|
1436
|
+
return out;
|
|
1128
1437
|
}
|
|
1129
|
-
function
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1438
|
+
function ints(n) {
|
|
1439
|
+
return new Array(n).fill("Int");
|
|
1440
|
+
}
|
|
1441
|
+
function vars(P, suffix) {
|
|
1442
|
+
const out = [];
|
|
1443
|
+
for (let i = 0; i < P; i++) out.push(`m${i}${suffix}`);
|
|
1444
|
+
return out;
|
|
1445
|
+
}
|
|
1446
|
+
function quantified(names) {
|
|
1447
|
+
return names.map((v) => `(${v} Int)`).join(" ");
|
|
1448
|
+
}
|
|
1449
|
+
function firingConditions(flatNet, ft, mVars, mpVars) {
|
|
1450
|
+
const P = flatNet.places.length;
|
|
1451
|
+
const conditions = [];
|
|
1133
1452
|
for (let i = 0; i < P; i++) {
|
|
1134
|
-
|
|
1135
|
-
mPrimeVars.push(Int.const(`mp${i}`));
|
|
1453
|
+
if (ft.preVector[i] > 0) conditions.push(`(>= ${mVars[i]} ${ft.preVector[i]})`);
|
|
1136
1454
|
}
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1455
|
+
for (const inh of ft.inhibitorPlaces) conditions.push(`(= ${mVars[inh]} 0)`);
|
|
1456
|
+
for (const rd of ft.readPlaces) conditions.push(`(>= ${mVars[rd]} 1)`);
|
|
1139
1457
|
for (let i = 0; i < P; i++) {
|
|
1140
|
-
if (i
|
|
1141
|
-
|
|
1458
|
+
if (ft.resetPlaces.includes(i) || ft.consumeAll[i]) {
|
|
1459
|
+
conditions.push(`(= ${mpVars[i]} ${ft.postVector[i]})`);
|
|
1142
1460
|
} else {
|
|
1143
|
-
|
|
1461
|
+
const delta = ft.postVector[i] - ft.preVector[i];
|
|
1462
|
+
if (delta > 0) conditions.push(`(= ${mpVars[i]} (+ ${mVars[i]} ${delta}))`);
|
|
1463
|
+
else if (delta < 0) conditions.push(`(= ${mpVars[i]} (- ${mVars[i]} ${-delta}))`);
|
|
1464
|
+
else conditions.push(`(= ${mpVars[i]} ${mVars[i]})`);
|
|
1144
1465
|
}
|
|
1145
1466
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
const head = reachable.call(...mPrimeVars);
|
|
1149
|
-
const qRule = ctx.ForAll([...mVars, ...mPrimeVars], ctx.Implies(body, head));
|
|
1150
|
-
fp.addRule(qRule, `env_inject_${idx}`);
|
|
1467
|
+
for (let i = 0; i < P; i++) conditions.push(`(>= ${mpVars[i]} 0)`);
|
|
1468
|
+
return conditions;
|
|
1151
1469
|
}
|
|
1152
|
-
function
|
|
1153
|
-
const
|
|
1154
|
-
for (const
|
|
1155
|
-
const
|
|
1156
|
-
if (
|
|
1470
|
+
function invariantConditions(invariants, names) {
|
|
1471
|
+
const conditions = [];
|
|
1472
|
+
for (const inv of invariants) {
|
|
1473
|
+
const terms = [...inv.support].sort((a, b) => a - b).map((i) => `(* ${inv.weights[i]} ${names[i]})`);
|
|
1474
|
+
if (terms.length === 0) continue;
|
|
1475
|
+
const sum = terms.length === 1 ? terms[0] : `(+ ${terms.join(" ")})`;
|
|
1476
|
+
conditions.push(`(= ${sum} ${inv.constant})`);
|
|
1157
1477
|
}
|
|
1158
|
-
return
|
|
1478
|
+
return conditions;
|
|
1159
1479
|
}
|
|
1160
|
-
function
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
result = ctx.And(result, mVars[p].ge(pre));
|
|
1480
|
+
function envBoundConditions(flatNet, mpVars) {
|
|
1481
|
+
return envBounds(flatNet).map(([pid, max]) => `(<= ${mpVars[pid]} ${max})`);
|
|
1482
|
+
}
|
|
1483
|
+
function injectionConditions(P, pid, bound, mVars, mpVars) {
|
|
1484
|
+
const conditions = [];
|
|
1485
|
+
if (bound != null) conditions.push(`(< ${mVars[pid]} ${bound})`);
|
|
1486
|
+
for (let i = 0; i < P; i++) {
|
|
1487
|
+
if (i === pid) conditions.push(`(= ${mpVars[i]} (+ ${mVars[i]} 1))`);
|
|
1488
|
+
else conditions.push(`(= ${mpVars[i]} ${mVars[i]})`);
|
|
1172
1489
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1490
|
+
return conditions;
|
|
1491
|
+
}
|
|
1492
|
+
function encodeTransitionRule(flatNet, ft, mVars, mpVars, invariants) {
|
|
1493
|
+
const conditions = [`(Reachable ${mVars.join(" ")})`];
|
|
1494
|
+
conditions.push(...firingConditions(flatNet, ft, mVars, mpVars));
|
|
1495
|
+
conditions.push(...invariantConditions(invariants, mpVars));
|
|
1496
|
+
conditions.push(...envBoundConditions(flatNet, mpVars));
|
|
1497
|
+
const body = `(and ${conditions.join("\n ")})`;
|
|
1498
|
+
return `(assert (forall (${quantified([...mVars, ...mpVars])})
|
|
1499
|
+
(=> ${body}
|
|
1500
|
+
(Reachable ${mpVars.join(" ")}))))`;
|
|
1501
|
+
}
|
|
1502
|
+
function encodeInjectionRule(P, pid, bound, mVars, mpVars) {
|
|
1503
|
+
const conditions = [`(Reachable ${mVars.join(" ")})`];
|
|
1504
|
+
conditions.push(...injectionConditions(P, pid, bound, mVars, mpVars));
|
|
1505
|
+
const body = `(and ${conditions.join("\n ")})`;
|
|
1506
|
+
return `(assert (forall (${quantified([...mVars, ...mpVars])})
|
|
1507
|
+
(=> ${body}
|
|
1508
|
+
(Reachable ${mpVars.join(" ")}))))`;
|
|
1509
|
+
}
|
|
1510
|
+
function conjoin(conditions) {
|
|
1511
|
+
if (conditions.length === 0) return "true";
|
|
1512
|
+
if (conditions.length === 1) return conditions[0];
|
|
1513
|
+
return `(and ${conditions.join(" ")})`;
|
|
1514
|
+
}
|
|
1515
|
+
function encodeStepRelationSmt2(flatNet) {
|
|
1516
|
+
const P = flatNet.places.length;
|
|
1517
|
+
const mVars = vars(P, "");
|
|
1518
|
+
const mpVars = vars(P, "p");
|
|
1519
|
+
const disjuncts = [];
|
|
1520
|
+
for (const ft of flatNet.transitions) {
|
|
1521
|
+
const conditions = firingConditions(flatNet, ft, mVars, mpVars);
|
|
1522
|
+
conditions.push(...envBoundConditions(flatNet, mpVars));
|
|
1523
|
+
disjuncts.push(conjoin(conditions));
|
|
1180
1524
|
}
|
|
1181
|
-
for (const
|
|
1182
|
-
|
|
1525
|
+
for (const inj of resolveEnvInjection(flatNet)) {
|
|
1526
|
+
disjuncts.push(conjoin(injectionConditions(P, inj.pid, inj.bound, mVars, mpVars)));
|
|
1183
1527
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1187
|
-
return result;
|
|
1528
|
+
if (disjuncts.length === 0) return "false";
|
|
1529
|
+
if (disjuncts.length === 1) return disjuncts[0];
|
|
1530
|
+
return `(or ${disjuncts.join("\n ")})`;
|
|
1188
1531
|
}
|
|
1189
|
-
function
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
result = ctx.And(result, mPrimeVars[p].eq(ft.postVector[p]));
|
|
1195
|
-
} else {
|
|
1196
|
-
const delta = ft.postVector[p] - ft.preVector[p];
|
|
1197
|
-
if (delta === 0) {
|
|
1198
|
-
result = ctx.And(result, mPrimeVars[p].eq(mVars[p]));
|
|
1199
|
-
} else {
|
|
1200
|
-
result = ctx.And(result, mPrimeVars[p].eq(mVars[p].add(delta)));
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
return result;
|
|
1532
|
+
function encodeErrorRule(flatNet, property, mVars, sinkPlaces, envInject) {
|
|
1533
|
+
const violation = encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject);
|
|
1534
|
+
return `(assert (forall (${quantified(mVars)})
|
|
1535
|
+
(=> (and (Reachable ${mVars.join(" ")}) ${violation})
|
|
1536
|
+
Error)))`;
|
|
1205
1537
|
}
|
|
1206
|
-
function
|
|
1207
|
-
const
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1538
|
+
function indexOrdered(flatNet, places) {
|
|
1539
|
+
const idx = /* @__PURE__ */ new Set();
|
|
1540
|
+
for (const place of places) {
|
|
1541
|
+
const i = flatNet.placeIndex.get(place.name);
|
|
1542
|
+
if (i != null) idx.add(i);
|
|
1211
1543
|
}
|
|
1212
|
-
|
|
1213
|
-
const violation = encodePropertyViolation(ctx, flatNet, property, sinkPlaces, mVars, P);
|
|
1214
|
-
const head = error.call();
|
|
1215
|
-
const body = ctx.And(reachBody, violation);
|
|
1216
|
-
const rule = ctx.Implies(body, head);
|
|
1217
|
-
const qRule = ctx.ForAll(mVars, rule);
|
|
1218
|
-
fp.addRule(qRule, `error_${property.type}`);
|
|
1544
|
+
return [...idx].sort((a, b) => a - b);
|
|
1219
1545
|
}
|
|
1220
|
-
function encodePropertyViolation(
|
|
1546
|
+
function encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject) {
|
|
1221
1547
|
switch (property.type) {
|
|
1222
|
-
case "deadlock-free":
|
|
1223
|
-
|
|
1224
|
-
if (sinkPlaces.size > 0) {
|
|
1225
|
-
let notAtSink = ctx.Bool.val(true);
|
|
1226
|
-
for (const sink of sinkPlaces) {
|
|
1227
|
-
const idx = flatNetIndexOf(flatNet, sink);
|
|
1228
|
-
if (idx >= 0) {
|
|
1229
|
-
notAtSink = ctx.And(notAtSink, mVars[idx].eq(0));
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
return ctx.And(deadlock, notAtSink);
|
|
1233
|
-
}
|
|
1234
|
-
return deadlock;
|
|
1235
|
-
}
|
|
1548
|
+
case "deadlock-free":
|
|
1549
|
+
return encodeDeadlock(flatNet, mVars, sinkPlaces, envInject);
|
|
1236
1550
|
case "mutual-exclusion": {
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
if (idx1 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p1.name}`);
|
|
1240
|
-
if (idx2 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p2.name}`);
|
|
1241
|
-
return ctx.And(mVars[idx1].ge(1), mVars[idx2].ge(1));
|
|
1242
|
-
}
|
|
1243
|
-
case "place-bound": {
|
|
1244
|
-
const idx = flatNetIndexOf(flatNet, property.place);
|
|
1245
|
-
if (idx < 0) throw new Error(`PlaceBound references unknown place: ${property.place.name}`);
|
|
1246
|
-
return mVars[idx].gt(property.bound);
|
|
1551
|
+
const conditions = indexOrdered(flatNet, [property.p1, property.p2]).map((i) => `(>= ${mVars[i]} 1)`);
|
|
1552
|
+
return conditions.length === 0 ? "false" : `(and ${conditions.join(" ")})`;
|
|
1247
1553
|
}
|
|
1554
|
+
case "place-bound":
|
|
1248
1555
|
case "branch-place-bound": {
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1556
|
+
const pid = flatNet.placeIndex.get(property.place.name);
|
|
1557
|
+
return pid == null ? "false" : `(> ${mVars[pid]} ${property.bound})`;
|
|
1558
|
+
}
|
|
1559
|
+
case "unreachable": {
|
|
1560
|
+
const conditions = indexOrdered(flatNet, property.places).map((i) => `(>= ${mVars[i]} 1)`);
|
|
1561
|
+
return conditions.length === 0 ? "false" : `(and ${conditions.join(" ")})`;
|
|
1252
1562
|
}
|
|
1253
1563
|
case "joined-or-dead-lettered": {
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
return ctx.And(deadlock, mVars[idx].ge(1));
|
|
1564
|
+
const deadlock = encodeDeadlock(flatNet, mVars, sinkPlaces, envInject);
|
|
1565
|
+
const pid = flatNet.placeIndex.get(property.pending.name);
|
|
1566
|
+
return pid == null ? "false" : `(and ${deadlock} (>= ${mVars[pid]} 1))`;
|
|
1258
1567
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
function encodeDeadlock(flatNet, mVars, sinkPlaces, envInject) {
|
|
1571
|
+
const envBound = /* @__PURE__ */ new Map();
|
|
1572
|
+
for (const inj of envInject) envBound.set(inj.pid, inj.bound);
|
|
1573
|
+
const disabledConditions = [];
|
|
1574
|
+
for (const ft of flatNet.transitions) {
|
|
1575
|
+
const disableReasons = [];
|
|
1576
|
+
let permanentlyDisabled = false;
|
|
1577
|
+
for (let i = 0; i < flatNet.places.length; i++) {
|
|
1578
|
+
if (ft.preVector[i] > 0) {
|
|
1579
|
+
if (envBound.has(i)) {
|
|
1580
|
+
const k = envBound.get(i);
|
|
1581
|
+
if (k != null && ft.preVector[i] > k) permanentlyDisabled = true;
|
|
1582
|
+
continue;
|
|
1265
1583
|
}
|
|
1584
|
+
disableReasons.push(`(< ${mVars[i]} ${ft.preVector[i]})`);
|
|
1266
1585
|
}
|
|
1267
|
-
return allMarked;
|
|
1268
1586
|
}
|
|
1587
|
+
for (const inh of ft.inhibitorPlaces) disableReasons.push(`(> ${mVars[inh]} 0)`);
|
|
1588
|
+
for (const rd of ft.readPlaces) {
|
|
1589
|
+
if (envBound.has(rd)) {
|
|
1590
|
+
const k = envBound.get(rd);
|
|
1591
|
+
if (k != null && k < 1) permanentlyDisabled = true;
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
disableReasons.push(`(< ${mVars[rd]} 1)`);
|
|
1595
|
+
}
|
|
1596
|
+
if (permanentlyDisabled) {
|
|
1597
|
+
disabledConditions.push("true");
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
if (disableReasons.length === 0) return "false";
|
|
1601
|
+
disabledConditions.push(`(or ${disableReasons.join(" ")})`);
|
|
1602
|
+
}
|
|
1603
|
+
for (const pid of indexOrdered(flatNet, sinkPlaces)) {
|
|
1604
|
+
disabledConditions.push(`(= ${mVars[pid]} 0)`);
|
|
1269
1605
|
}
|
|
1606
|
+
return disabledConditions.length === 0 ? "true" : `(and ${disabledConditions.join("\n ")})`;
|
|
1270
1607
|
}
|
|
1271
|
-
function
|
|
1272
|
-
|
|
1273
|
-
for (const
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1608
|
+
function injectionMap(flatNet) {
|
|
1609
|
+
const out = /* @__PURE__ */ new Map();
|
|
1610
|
+
for (const inj of resolveEnvInjection(flatNet)) out.set(inj.pid, inj.bound);
|
|
1611
|
+
return out;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// src/verification/z3/certificate-checker.ts
|
|
1615
|
+
var VC_LABELS = ["initiation (VC1)", "consecution (VC2)", "safety (VC3)"];
|
|
1616
|
+
async function checkCertificate(certificate, flatNet, initialMarking, property, invariants, sinkPlaces, solver, timeoutMs) {
|
|
1617
|
+
if (certificate == null) {
|
|
1618
|
+
return {
|
|
1619
|
+
type: "unavailable",
|
|
1620
|
+
reason: "no inductive invariant (define-fun block) could be extracted from the z3 model",
|
|
1621
|
+
invariant: null
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
const shape = shapeFailure(flatNet, invariants);
|
|
1625
|
+
if (shape != null) return { type: "unavailable", reason: shape, invariant: certificate };
|
|
1626
|
+
if (!certificate.includes("(define-fun Reachable ") && !certificate.includes("(define-fun |Reachable| ")) {
|
|
1627
|
+
return { type: "unavailable", reason: "certificate does not define Reachable", invariant: certificate };
|
|
1628
|
+
}
|
|
1629
|
+
const vcs = buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants);
|
|
1630
|
+
let results;
|
|
1631
|
+
try {
|
|
1632
|
+
results = await runVcScript(script(vcs), timeoutMs, solver);
|
|
1633
|
+
} catch (e) {
|
|
1634
|
+
return { type: "unavailable", reason: String(e?.message ?? e), invariant: certificate };
|
|
1635
|
+
}
|
|
1636
|
+
for (let i = 0; i < results.length; i++) {
|
|
1637
|
+
if (results[i] !== "unsat") {
|
|
1638
|
+
const detail = await detailFor(vcs, i, results[i], flatNet, timeoutMs, solver);
|
|
1639
|
+
return { type: "failed", vc: VC_LABELS[i], detail, invariant: certificate };
|
|
1640
|
+
}
|
|
1284
1641
|
}
|
|
1285
|
-
return
|
|
1642
|
+
return { type: "passed", invariant: certificate };
|
|
1286
1643
|
}
|
|
1287
|
-
function
|
|
1288
|
-
|
|
1644
|
+
function vcScript(certificate, flatNet, initialMarking, property, sinkPlaces, invariants) {
|
|
1645
|
+
return script(buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants));
|
|
1646
|
+
}
|
|
1647
|
+
function shapeFailure(flatNet, invariants) {
|
|
1648
|
+
const P = flatNet.places.length;
|
|
1289
1649
|
for (const inv of invariants) {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
}
|
|
1650
|
+
if (inv.weights.length !== P) {
|
|
1651
|
+
return `P-invariant has ${inv.weights.length} weights for a ${P}-place net`;
|
|
1652
|
+
}
|
|
1653
|
+
for (const pid of inv.support) {
|
|
1654
|
+
if (pid >= P || pid < 0) return `P-invariant support names place index ${pid} in a ${P}-place net`;
|
|
1295
1655
|
}
|
|
1296
|
-
result = ctx.And(result, sum.eq(inv.constant));
|
|
1297
1656
|
}
|
|
1298
|
-
return
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
var VcFailure = class extends Error {
|
|
1660
|
+
};
|
|
1661
|
+
async function runVcScript(text, timeoutMs, solver) {
|
|
1662
|
+
const reply = await runZ3Text(solver, text, "certificate", timeoutMs, []);
|
|
1663
|
+
const budget = timeoutBudget(timeoutMs);
|
|
1664
|
+
const err = errorLine(reply.stderr);
|
|
1665
|
+
if (err != null) throw new VcFailure(`z3 reported an error on stderr: ${err}`);
|
|
1666
|
+
if (timeoutLine(reply.stdout)) {
|
|
1667
|
+
throw new VcFailure(`z3 hard timeout after ${hardTimeoutSecs(budget)}s while checking the certificate`);
|
|
1668
|
+
}
|
|
1669
|
+
if (reply.exit.kind === "killed") {
|
|
1670
|
+
throw new VcFailure(`z3 did not exit within ${watchdogMs(budget)} ms while checking the certificate and was killed`);
|
|
1671
|
+
}
|
|
1672
|
+
const results = parseVcResults(reply.stdout);
|
|
1673
|
+
if (!replySucceeded(reply)) {
|
|
1674
|
+
const status = reply.exit.kind === "exited" ? `exit status: ${reply.exit.code}` : "the watchdog kill";
|
|
1675
|
+
throw new VcFailure(`z3 exited with ${status} after answering [${results.join(", ")}]`);
|
|
1676
|
+
}
|
|
1677
|
+
return results;
|
|
1678
|
+
}
|
|
1679
|
+
function parseVcResults(stdout) {
|
|
1680
|
+
const err = errorLine(stdout);
|
|
1681
|
+
if (err != null) throw new VcFailure(`z3 error while checking the certificate: ${err}`);
|
|
1682
|
+
if (timeoutLine(stdout)) throw new VcFailure("z3 hard timeout while checking the certificate");
|
|
1683
|
+
const results = stdout.split("\n").map((l) => l.trim()).filter((l) => l === "sat" || l === "unsat" || l === "unknown");
|
|
1684
|
+
if (results.length !== 3) {
|
|
1685
|
+
throw new VcFailure(`expected 3 VC answers from z3, got ${results.length}: [${results.join(", ")}]`);
|
|
1686
|
+
}
|
|
1687
|
+
return results;
|
|
1688
|
+
}
|
|
1689
|
+
function buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants) {
|
|
1690
|
+
const P = flatNet.places.length;
|
|
1691
|
+
const mVars = [];
|
|
1692
|
+
const mpVars = [];
|
|
1693
|
+
for (let i = 0; i < P; i++) {
|
|
1694
|
+
mVars.push(`m${i}`);
|
|
1695
|
+
mpVars.push(`m${i}p`);
|
|
1696
|
+
}
|
|
1697
|
+
const prelude = [
|
|
1698
|
+
"; IC3/PDR certificate check (plain SMT-LIB2, not HORN):",
|
|
1699
|
+
"; each VC below must be unsat for the certificate to stand.",
|
|
1700
|
+
certificate,
|
|
1701
|
+
""
|
|
1702
|
+
];
|
|
1703
|
+
for (const v of mVars) prelude.push(`(declare-const ${v} Int)`);
|
|
1704
|
+
for (const v of mpVars) prelude.push(`(declare-const ${v} Int)`);
|
|
1705
|
+
const m0 = [];
|
|
1706
|
+
for (let i = 0; i < P; i++) m0.push(String(initialMarking.tokens(flatNet.places[i])));
|
|
1707
|
+
const vc1 = [`(assert (not ${candidate(m0, invariants)}))`];
|
|
1708
|
+
const nonNegative = mVars.map((v) => `(assert (>= ${v} 0))`);
|
|
1709
|
+
const step = encodeStepRelationSmt2(flatNet);
|
|
1710
|
+
const vc2 = [
|
|
1711
|
+
...nonNegative,
|
|
1712
|
+
`(assert ${candidate(mVars, invariants)})`,
|
|
1713
|
+
`(assert ${step})`,
|
|
1714
|
+
`(assert (not ${candidate(mpVars, invariants)}))`
|
|
1715
|
+
];
|
|
1716
|
+
const bad = encodePropertyViolation(flatNet, property, mVars, sinkPlaces, resolveEnvInjection(flatNet));
|
|
1717
|
+
const vc3 = [...nonNegative, `(assert ${candidate(mVars, invariants)})`, `(assert ${bad})`];
|
|
1718
|
+
return { prelude, asserts: [vc1, vc2, vc3] };
|
|
1719
|
+
}
|
|
1720
|
+
function script(vcs) {
|
|
1721
|
+
const lines = [...vcs.prelude];
|
|
1722
|
+
for (let i = 0; i < vcs.asserts.length; i++) {
|
|
1723
|
+
lines.push("");
|
|
1724
|
+
lines.push(`; VC${i + 1} ${VC_LABELS[i]}`);
|
|
1725
|
+
lines.push("(push)");
|
|
1726
|
+
lines.push(...vcs.asserts[i]);
|
|
1727
|
+
lines.push("(check-sat)");
|
|
1728
|
+
lines.push("(pop)");
|
|
1729
|
+
}
|
|
1730
|
+
return lines.join("\n");
|
|
1731
|
+
}
|
|
1732
|
+
async function detailFor(vcs, i, answer, flatNet, timeoutMs, solver) {
|
|
1733
|
+
const lines = ["(set-option :produce-models true)", ...vcs.prelude, ...vcs.asserts[i], "(check-sat)"];
|
|
1734
|
+
lines.push(answer === "sat" ? "(get-model)" : "(get-info :reason-unknown)");
|
|
1735
|
+
let reply = "";
|
|
1736
|
+
try {
|
|
1737
|
+
reply = (await runZ3Text(solver, lines.join("\n"), "certificate-detail", timeoutMs, [])).stdout;
|
|
1738
|
+
} catch {
|
|
1739
|
+
reply = "";
|
|
1740
|
+
}
|
|
1741
|
+
if (answer === "sat") {
|
|
1742
|
+
const w = witness(reply, flatNet);
|
|
1743
|
+
return w == null ? "solver returned SATISFIABLE" : `solver returned SATISFIABLE (witness: ${w})`;
|
|
1744
|
+
}
|
|
1745
|
+
const r = reasonUnknown(reply);
|
|
1746
|
+
return r == null ? "solver returned UNKNOWN" : `solver returned UNKNOWN (${r})`;
|
|
1747
|
+
}
|
|
1748
|
+
function witness(model, flatNet) {
|
|
1749
|
+
const parts = [];
|
|
1750
|
+
for (let i = 0; i < flatNet.places.length; i++) {
|
|
1751
|
+
const needle = `(define-fun m${i} () Int`;
|
|
1752
|
+
const at = model.indexOf(needle);
|
|
1753
|
+
if (at < 0) continue;
|
|
1754
|
+
const rest = model.slice(at + needle.length).trimStart();
|
|
1755
|
+
let value;
|
|
1756
|
+
if (rest.startsWith("(")) {
|
|
1757
|
+
const end = sexprEnd(rest, 0);
|
|
1758
|
+
if (end < 0) continue;
|
|
1759
|
+
value = rest.slice(1, end - 1).trim().split(/\s+/).join("");
|
|
1760
|
+
} else {
|
|
1761
|
+
let end = 0;
|
|
1762
|
+
while (end < rest.length && !/\s/.test(rest[end]) && rest[end] !== ")") end++;
|
|
1763
|
+
if (end === 0) continue;
|
|
1764
|
+
value = rest.slice(0, end);
|
|
1765
|
+
}
|
|
1766
|
+
parts.push(`${flatNet.places[i].name}=${value}`);
|
|
1767
|
+
}
|
|
1768
|
+
return parts.length === 0 ? null : parts.join(", ");
|
|
1769
|
+
}
|
|
1770
|
+
function reasonUnknown(reply) {
|
|
1771
|
+
const at = reply.indexOf(":reason-unknown");
|
|
1772
|
+
if (at < 0) return null;
|
|
1773
|
+
const rest = reply.slice(at + ":reason-unknown".length).trimStart();
|
|
1774
|
+
const end = rest.indexOf(")");
|
|
1775
|
+
if (end < 0) return null;
|
|
1776
|
+
let reason = rest.slice(0, end).trim();
|
|
1777
|
+
if (reason.startsWith('"') && reason.endsWith('"') && reason.length >= 2) reason = reason.slice(1, -1);
|
|
1778
|
+
reason = reason.trim();
|
|
1779
|
+
return reason === "" ? null : reason;
|
|
1780
|
+
}
|
|
1781
|
+
function candidate(names, invariants) {
|
|
1782
|
+
return conjoin([`(Reachable ${names.join(" ")})`, ...invariantConditions(invariants, names)]);
|
|
1299
1783
|
}
|
|
1300
1784
|
|
|
1301
1785
|
// src/verification/analysis/dbm.ts
|
|
@@ -1831,66 +2315,321 @@ function consumeFromPlace(builder, place, count, environmentPlaces, environmentM
|
|
|
1831
2315
|
}
|
|
1832
2316
|
|
|
1833
2317
|
// src/verification/z3/counterexample-decoder.ts
|
|
1834
|
-
function decode(
|
|
1835
|
-
const
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
2318
|
+
function decode(answer, flatNet) {
|
|
2319
|
+
const states = decodeStateSet(answer, flatNet);
|
|
2320
|
+
return { states, note: states.size === 0 ? "no ground Reachable states in the z3 proof" : null };
|
|
2321
|
+
}
|
|
2322
|
+
function decodeStateSet(answer, flatNet) {
|
|
2323
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2324
|
+
const P = flatNet.places.length;
|
|
2325
|
+
for (const head of ["(Reachable", "(|Reachable|"]) {
|
|
2326
|
+
let from = 0;
|
|
2327
|
+
for (; ; ) {
|
|
2328
|
+
const start = answer.indexOf(head, from);
|
|
2329
|
+
if (start < 0) break;
|
|
2330
|
+
from = start + head.length;
|
|
2331
|
+
if (head === "(Reachable") {
|
|
2332
|
+
const next = answer[from];
|
|
2333
|
+
if (next == null || !(/\s/.test(next) || next === ")")) continue;
|
|
2334
|
+
}
|
|
2335
|
+
const end = sexprEnd(answer, start);
|
|
2336
|
+
if (end < 0) break;
|
|
2337
|
+
const inner = answer.slice(start + head.length, end - 1);
|
|
2338
|
+
const args = parseGroundIntArgs(inner);
|
|
2339
|
+
if (args != null && args.length === P) {
|
|
2340
|
+
const marking = toMarking(args, flatNet);
|
|
2341
|
+
const key = marking.toString();
|
|
2342
|
+
if (!byKey.has(key)) byKey.set(key, marking);
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
1839
2345
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2346
|
+
return new Set(byKey.values());
|
|
2347
|
+
}
|
|
2348
|
+
function toMarking(args, flatNet) {
|
|
2349
|
+
const builder = MarkingState.builder();
|
|
2350
|
+
for (let i = 0; i < args.length; i++) {
|
|
2351
|
+
if (args[i] > 0) builder.tokens(flatNet.places[i], args[i]);
|
|
2352
|
+
}
|
|
2353
|
+
return builder.build();
|
|
2354
|
+
}
|
|
2355
|
+
function parseGroundIntArgs(inner) {
|
|
2356
|
+
const args = [];
|
|
2357
|
+
let rest = inner.trimStart();
|
|
2358
|
+
while (rest !== "") {
|
|
2359
|
+
if (rest.startsWith("(")) {
|
|
2360
|
+
const stripped = rest.slice(1);
|
|
2361
|
+
const close = stripped.indexOf(")");
|
|
2362
|
+
if (close < 0) return null;
|
|
2363
|
+
const body = stripped.slice(0, close);
|
|
2364
|
+
if (body.includes("(")) return null;
|
|
2365
|
+
const trimmed = body.trim();
|
|
2366
|
+
if (!trimmed.startsWith("-")) return null;
|
|
2367
|
+
const n = parseInt64(trimmed.slice(1).trim());
|
|
2368
|
+
if (n == null) return null;
|
|
2369
|
+
args.push(-n);
|
|
2370
|
+
rest = stripped.slice(close + 1).trimStart();
|
|
2371
|
+
} else {
|
|
2372
|
+
let tokenEnd = rest.length;
|
|
2373
|
+
for (let i = 0; i < rest.length; i++) {
|
|
2374
|
+
const c = rest[i];
|
|
2375
|
+
if (/\s/.test(c) || c === "(" || c === ")") {
|
|
2376
|
+
tokenEnd = i;
|
|
2377
|
+
break;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
const n = parseInt64(rest.slice(0, tokenEnd));
|
|
2381
|
+
if (n == null) return null;
|
|
2382
|
+
args.push(n);
|
|
2383
|
+
rest = rest.slice(tokenEnd).trimStart();
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
return args;
|
|
2387
|
+
}
|
|
2388
|
+
function parseInt64(token) {
|
|
2389
|
+
return /^-?\d+$/.test(token) ? Number(token) : null;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// src/verification/encoding/flat-net.ts
|
|
2393
|
+
function flatNetPlaceCount(net) {
|
|
2394
|
+
return net.places.length;
|
|
2395
|
+
}
|
|
2396
|
+
function flatNetTransitionCount(net) {
|
|
2397
|
+
return net.transitions.length;
|
|
2398
|
+
}
|
|
2399
|
+
function flatNetIndexOf(net, place) {
|
|
2400
|
+
return net.placeIndex.get(place.name) ?? -1;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
// src/verification/z3/abstract-replayer.ts
|
|
2404
|
+
function stepName(step) {
|
|
2405
|
+
return step.kind === "fire" ? step.transition : `inject(${step.place})`;
|
|
2406
|
+
}
|
|
2407
|
+
function stateKey(state) {
|
|
2408
|
+
return state.join(",");
|
|
2409
|
+
}
|
|
2410
|
+
function vectorize(marking, flatNet) {
|
|
2411
|
+
return flatNet.places.map((p) => marking.tokens(p));
|
|
2412
|
+
}
|
|
2413
|
+
function toMarkingState(state, flatNet) {
|
|
2414
|
+
const builder = MarkingState.builder();
|
|
2415
|
+
for (let i = 0; i < flatNet.places.length; i++) {
|
|
2416
|
+
if (state[i] > 0) builder.tokens(flatNet.places[i], state[i]);
|
|
2417
|
+
}
|
|
2418
|
+
return builder.build();
|
|
2419
|
+
}
|
|
2420
|
+
function enabledA(state, ft) {
|
|
2421
|
+
const P = state.length;
|
|
2422
|
+
for (let p = 0; p < P; p++) {
|
|
2423
|
+
if (ft.preVector[p] > 0 && state[p] < ft.preVector[p]) return false;
|
|
2424
|
+
}
|
|
2425
|
+
for (const p of ft.readPlaces) {
|
|
2426
|
+
if (state[p] < 1) return false;
|
|
2427
|
+
}
|
|
2428
|
+
for (const p of ft.inhibitorPlaces) {
|
|
2429
|
+
if (state[p] !== 0) return false;
|
|
2430
|
+
}
|
|
2431
|
+
return true;
|
|
2432
|
+
}
|
|
2433
|
+
function fireIndexed(state, ft, resets) {
|
|
2434
|
+
const P = state.length;
|
|
2435
|
+
const next = new Array(P);
|
|
2436
|
+
for (let p = 0; p < P; p++) {
|
|
2437
|
+
if (resets.has(p) || ft.consumeAll[p]) {
|
|
2438
|
+
next[p] = ft.postVector[p];
|
|
2439
|
+
} else {
|
|
2440
|
+
next[p] = state[p] - ft.preVector[p] + ft.postVector[p];
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
return next;
|
|
2444
|
+
}
|
|
2445
|
+
function injectA(state, idx) {
|
|
2446
|
+
const next = [...state];
|
|
2447
|
+
next[idx] = next[idx] + 1;
|
|
2448
|
+
return next;
|
|
2449
|
+
}
|
|
2450
|
+
function buildIndex(flatNet) {
|
|
2451
|
+
const resetSets = flatNet.transitions.map((ft) => new Set(ft.resetPlaces));
|
|
2452
|
+
const envInj = /* @__PURE__ */ new Map();
|
|
2453
|
+
for (const [name, bound] of flatNet.environmentInjection) {
|
|
2454
|
+
const idx = flatNet.placeIndex.get(name);
|
|
2455
|
+
if (idx != null) envInj.set(idx, bound);
|
|
2456
|
+
}
|
|
2457
|
+
const envCaps = [];
|
|
2458
|
+
for (const [name, cap] of flatNet.environmentBounds) {
|
|
2459
|
+
const idx = flatNet.placeIndex.get(name);
|
|
2460
|
+
if (idx != null) envCaps.push([idx, cap]);
|
|
2461
|
+
}
|
|
2462
|
+
return { flatNet, resetSets, envInj, envCaps };
|
|
2463
|
+
}
|
|
2464
|
+
function withinEnvBounds(index, state) {
|
|
2465
|
+
for (const [idx, cap] of index.envCaps) {
|
|
2466
|
+
if (state[idx] > cap) return false;
|
|
2467
|
+
}
|
|
2468
|
+
return true;
|
|
2469
|
+
}
|
|
2470
|
+
function successorsIndexed(index, state) {
|
|
2471
|
+
const out = [];
|
|
2472
|
+
const transitions = index.flatNet.transitions;
|
|
2473
|
+
for (let t = 0; t < transitions.length; t++) {
|
|
2474
|
+
const ft = transitions[t];
|
|
2475
|
+
if (!enabledA(state, ft)) continue;
|
|
2476
|
+
const next = fireIndexed(state, ft, index.resetSets[t]);
|
|
2477
|
+
if (!withinEnvBounds(index, next)) continue;
|
|
2478
|
+
out.push({ state: next, step: { kind: "fire", transition: ft.name } });
|
|
2479
|
+
}
|
|
2480
|
+
for (const [name, bound] of index.flatNet.environmentInjection) {
|
|
2481
|
+
const idx = index.flatNet.placeIndex.get(name);
|
|
2482
|
+
if (idx == null) continue;
|
|
2483
|
+
if (bound === null || state[idx] < bound) {
|
|
2484
|
+
out.push({ state: injectA(state, idx), step: { kind: "inject", place: name } });
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return out;
|
|
2488
|
+
}
|
|
2489
|
+
function enabledRelaxEnv(state, ft, envInj) {
|
|
2490
|
+
const P = state.length;
|
|
2491
|
+
for (let p = 0; p < P; p++) {
|
|
2492
|
+
const pre = ft.preVector[p];
|
|
2493
|
+
if (pre <= 0) continue;
|
|
2494
|
+
if (envInj.has(p)) {
|
|
2495
|
+
const bound = envInj.get(p);
|
|
2496
|
+
if (bound !== null && pre > bound) return false;
|
|
2497
|
+
continue;
|
|
2498
|
+
}
|
|
2499
|
+
if (state[p] < pre) return false;
|
|
2500
|
+
}
|
|
2501
|
+
for (const p of ft.readPlaces) {
|
|
2502
|
+
if (envInj.has(p)) {
|
|
2503
|
+
const bound = envInj.get(p);
|
|
2504
|
+
if (bound !== null && bound < 1) return false;
|
|
2505
|
+
continue;
|
|
2506
|
+
}
|
|
2507
|
+
if (state[p] < 1) return false;
|
|
1843
2508
|
}
|
|
1844
|
-
|
|
2509
|
+
for (const p of ft.inhibitorPlaces) {
|
|
2510
|
+
if (state[p] !== 0) return false;
|
|
2511
|
+
}
|
|
2512
|
+
return true;
|
|
1845
2513
|
}
|
|
1846
|
-
function
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
let name;
|
|
1850
|
-
try {
|
|
1851
|
-
const decl = expr.decl();
|
|
1852
|
-
name = String(decl.name());
|
|
1853
|
-
} catch {
|
|
1854
|
-
return;
|
|
2514
|
+
function isDeadlockA(index, state) {
|
|
2515
|
+
for (const ft of index.flatNet.transitions) {
|
|
2516
|
+
if (enabledRelaxEnv(state, ft, index.envInj)) return false;
|
|
1855
2517
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2518
|
+
return true;
|
|
2519
|
+
}
|
|
2520
|
+
function satisfiesBadIndexed(index, state, property, sinkPlaces) {
|
|
2521
|
+
const flatNet = index.flatNet;
|
|
2522
|
+
switch (property.type) {
|
|
2523
|
+
case "deadlock-free": {
|
|
2524
|
+
if (!isDeadlockA(index, state)) return false;
|
|
2525
|
+
for (const sink of sinkPlaces) {
|
|
2526
|
+
const idx = flatNetIndexOf(flatNet, sink);
|
|
2527
|
+
if (idx >= 0 && state[idx] > 0) return false;
|
|
1863
2528
|
}
|
|
2529
|
+
return true;
|
|
1864
2530
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2531
|
+
case "mutual-exclusion": {
|
|
2532
|
+
const idx1 = flatNetIndexOf(flatNet, property.p1);
|
|
2533
|
+
const idx2 = flatNetIndexOf(flatNet, property.p2);
|
|
2534
|
+
if (idx1 < 0 || idx2 < 0) return false;
|
|
2535
|
+
return state[idx1] >= 1 && state[idx2] >= 1;
|
|
2536
|
+
}
|
|
2537
|
+
case "place-bound":
|
|
2538
|
+
case "branch-place-bound": {
|
|
2539
|
+
const idx = flatNetIndexOf(flatNet, property.place);
|
|
2540
|
+
if (idx < 0) return false;
|
|
2541
|
+
return state[idx] > property.bound;
|
|
2542
|
+
}
|
|
2543
|
+
case "joined-or-dead-lettered": {
|
|
2544
|
+
const idx = flatNetIndexOf(flatNet, property.pending);
|
|
2545
|
+
if (idx < 0) return false;
|
|
2546
|
+
return isDeadlockA(index, state) && state[idx] >= 1;
|
|
2547
|
+
}
|
|
2548
|
+
case "unreachable": {
|
|
2549
|
+
let resolved = 0;
|
|
2550
|
+
for (const p of property.places) {
|
|
2551
|
+
const idx = flatNetIndexOf(flatNet, p);
|
|
2552
|
+
if (idx < 0) continue;
|
|
2553
|
+
resolved++;
|
|
2554
|
+
if (state[idx] < 1) return false;
|
|
2555
|
+
}
|
|
2556
|
+
return resolved > 0;
|
|
1871
2557
|
}
|
|
1872
|
-
} catch {
|
|
1873
|
-
}
|
|
1874
|
-
if (name.startsWith("t_")) {
|
|
1875
|
-
transitions.push(name.substring(2));
|
|
1876
2558
|
}
|
|
1877
2559
|
}
|
|
1878
|
-
function
|
|
1879
|
-
const
|
|
1880
|
-
|
|
1881
|
-
const
|
|
1882
|
-
for (
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2560
|
+
function replayCounterexample(flatNet, initial, decodedStates, property, sinkPlaces, options = {}) {
|
|
2561
|
+
const segmentBudget = options.segmentBudget ?? 3;
|
|
2562
|
+
const nodeBudget = options.nodeBudget ?? 1e4;
|
|
2563
|
+
const anchors = /* @__PURE__ */ new Set();
|
|
2564
|
+
for (const s of decodedStates) anchors.add(stateKey(s));
|
|
2565
|
+
if (anchors.size === 0) {
|
|
2566
|
+
return { kind: "exhausted", reason: "no decoded states to replay", nodesExplored: 0 };
|
|
2567
|
+
}
|
|
2568
|
+
const initKey = stateKey(initial);
|
|
2569
|
+
if (!anchors.has(initKey)) {
|
|
2570
|
+
return {
|
|
2571
|
+
kind: "exhausted",
|
|
2572
|
+
reason: "the initial marking is not among the decoded states",
|
|
2573
|
+
nodesExplored: 0
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
const index = buildIndex(flatNet);
|
|
2577
|
+
if (satisfiesBadIndexed(index, initial, property, sinkPlaces)) {
|
|
2578
|
+
return { kind: "confirmed", states: [initial], steps: [], nodesExplored: 1 };
|
|
2579
|
+
}
|
|
2580
|
+
const nodes = [{ state: initial, step: null, parent: -1, segment: 0 }];
|
|
2581
|
+
const bestSegment = /* @__PURE__ */ new Map([[initKey, 0]]);
|
|
2582
|
+
const queue = [0];
|
|
2583
|
+
let truncated = false;
|
|
2584
|
+
for (let head = 0; head < queue.length; head++) {
|
|
2585
|
+
const idx = queue[head];
|
|
2586
|
+
const node = nodes[idx];
|
|
2587
|
+
if (node.segment >= segmentBudget) {
|
|
2588
|
+
truncated = true;
|
|
2589
|
+
continue;
|
|
2590
|
+
}
|
|
2591
|
+
for (const succ of successorsIndexed(index, node.state)) {
|
|
2592
|
+
const key = stateKey(succ.state);
|
|
2593
|
+
const segment = anchors.has(key) ? 0 : node.segment + 1;
|
|
2594
|
+
const prior = bestSegment.get(key);
|
|
2595
|
+
if (prior !== void 0 && prior <= segment) continue;
|
|
2596
|
+
bestSegment.set(key, segment);
|
|
2597
|
+
if (nodes.length >= nodeBudget) {
|
|
2598
|
+
return {
|
|
2599
|
+
kind: "exhausted",
|
|
2600
|
+
reason: `search budget exhausted (${nodeBudget} nodes) before reaching a violating state`,
|
|
2601
|
+
nodesExplored: nodes.length
|
|
2602
|
+
};
|
|
1888
2603
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
2604
|
+
nodes.push({ state: succ.state, step: succ.step, parent: idx, segment });
|
|
2605
|
+
const childIdx = nodes.length - 1;
|
|
2606
|
+
if (satisfiesBadIndexed(index, succ.state, property, sinkPlaces)) {
|
|
2607
|
+
const chain = reconstruct(nodes, childIdx);
|
|
2608
|
+
return { kind: "confirmed", ...chain, nodesExplored: nodes.length };
|
|
2609
|
+
}
|
|
2610
|
+
queue.push(childIdx);
|
|
1891
2611
|
}
|
|
1892
2612
|
}
|
|
1893
|
-
|
|
2613
|
+
if (truncated) {
|
|
2614
|
+
return {
|
|
2615
|
+
kind: "exhausted",
|
|
2616
|
+
reason: `no violating state within ${segmentBudget} abstract step(s) of a decoded state (${bestSegment.size} state(s) explored)`,
|
|
2617
|
+
nodesExplored: nodes.length
|
|
2618
|
+
};
|
|
2619
|
+
}
|
|
2620
|
+
return { kind: "no-chain", nodesExplored: nodes.length };
|
|
2621
|
+
}
|
|
2622
|
+
function reconstruct(nodes, last) {
|
|
2623
|
+
const states = [];
|
|
2624
|
+
const steps = [];
|
|
2625
|
+
for (let i = last; i >= 0; i = nodes[i].parent) {
|
|
2626
|
+
const node = nodes[i];
|
|
2627
|
+
states.push(node.state);
|
|
2628
|
+
if (node.step != null) steps.push(node.step);
|
|
2629
|
+
}
|
|
2630
|
+
states.reverse();
|
|
2631
|
+
steps.reverse();
|
|
2632
|
+
return { states, steps };
|
|
1894
2633
|
}
|
|
1895
2634
|
|
|
1896
2635
|
// src/verification/z3/name-coloured-encoder.ts
|
|
@@ -1899,25 +2638,29 @@ function colourSlotBound(coloured, semiflows) {
|
|
|
1899
2638
|
const isSemiflow = (inv) => inv.weights.every((x) => x >= 0);
|
|
1900
2639
|
let single = null;
|
|
1901
2640
|
for (const inv of semiflows) {
|
|
1902
|
-
if (isSemiflow(inv) &&
|
|
2641
|
+
if (isSemiflow(inv) && coloured.every((pid) => w(inv, pid) >= 1)) {
|
|
1903
2642
|
if (single === null || inv.constant < single) single = inv.constant;
|
|
1904
2643
|
}
|
|
1905
2644
|
}
|
|
1906
2645
|
if (single !== null) return single;
|
|
1907
|
-
let sumConst = 0;
|
|
1908
2646
|
const covered = new Array(coloured.length).fill(false);
|
|
1909
2647
|
for (const inv of semiflows) {
|
|
1910
|
-
if (!isSemiflow(inv)) continue;
|
|
1911
|
-
let touches = false;
|
|
2648
|
+
if (!isSemiflow(inv) || inv.constant !== 0) continue;
|
|
1912
2649
|
for (let i = 0; i < coloured.length; i++) {
|
|
1913
|
-
if (w(inv, coloured[i]) >= 1)
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
2650
|
+
if (w(inv, coloured[i]) >= 1) covered[i] = true;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
const free = [...covered];
|
|
2654
|
+
let sumConst = 0;
|
|
2655
|
+
for (const inv of semiflows) {
|
|
2656
|
+
if (!isSemiflow(inv) || inv.constant === 0) continue;
|
|
2657
|
+
if (!coloured.some((pid, i) => !free[i] && w(inv, pid) >= 1)) continue;
|
|
2658
|
+
for (let i = 0; i < coloured.length; i++) {
|
|
2659
|
+
if (w(inv, coloured[i]) >= 1) covered[i] = true;
|
|
1917
2660
|
}
|
|
1918
|
-
|
|
2661
|
+
sumConst += inv.constant;
|
|
1919
2662
|
}
|
|
1920
|
-
if (covered.every((c) => c)
|
|
2663
|
+
if (covered.every((c) => c)) return sumConst;
|
|
1921
2664
|
return null;
|
|
1922
2665
|
}
|
|
1923
2666
|
function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrierPlaces, semiflows) {
|
|
@@ -1947,6 +2690,7 @@ function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrie
|
|
|
1947
2690
|
}
|
|
1948
2691
|
const k = colourSlotBound(coloured, semiflows);
|
|
1949
2692
|
if (k === null) return null;
|
|
2693
|
+
if (k === 0 && coloured.length === P) return null;
|
|
1950
2694
|
const budgetIdx = /* @__PURE__ */ new Set();
|
|
1951
2695
|
for (const n of budgetNames) {
|
|
1952
2696
|
const i = flat.placeIndex.get(n);
|
|
@@ -1982,324 +2726,278 @@ function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrie
|
|
|
1982
2726
|
}
|
|
1983
2727
|
return { coloured, isColoured, k, classes };
|
|
1984
2728
|
}
|
|
1985
|
-
function buildLayout(
|
|
2729
|
+
function buildLayout(plan, P) {
|
|
1986
2730
|
const colUnc = new Array(P).fill(-1);
|
|
1987
2731
|
const colCol = Array.from({ length: P }, () => []);
|
|
1988
|
-
|
|
2732
|
+
const cur = [];
|
|
2733
|
+
const nxt = [];
|
|
1989
2734
|
for (let i = 0; i < P; i++) {
|
|
1990
2735
|
if (plan.isColoured[i]) {
|
|
1991
2736
|
const idxs = [];
|
|
1992
|
-
for (let c = 0; c < plan.k; c++)
|
|
2737
|
+
for (let c = 0; c < plan.k; c++) {
|
|
2738
|
+
idxs.push(cur.length);
|
|
2739
|
+
cur.push(`m${i}_${c}`);
|
|
2740
|
+
nxt.push(`m${i}_${c}p`);
|
|
2741
|
+
}
|
|
1993
2742
|
colCol[i] = idxs;
|
|
1994
2743
|
} else {
|
|
1995
|
-
colUnc[i] =
|
|
2744
|
+
colUnc[i] = cur.length;
|
|
2745
|
+
cur.push(`m${i}`);
|
|
2746
|
+
nxt.push(`m${i}p`);
|
|
1996
2747
|
}
|
|
1997
2748
|
}
|
|
1998
|
-
|
|
1999
|
-
const nxt = [];
|
|
2000
|
-
for (let col = 0; col < nCols; col++) {
|
|
2001
|
-
cur.push(ctx.Int.const(`c${col}`));
|
|
2002
|
-
nxt.push(ctx.Int.const(`cp${col}`));
|
|
2003
|
-
}
|
|
2004
|
-
return { colUnc, colCol, nCols, cur, nxt };
|
|
2749
|
+
return { colUnc, colCol, cur, nxt };
|
|
2005
2750
|
}
|
|
2006
|
-
function
|
|
2751
|
+
function quantified2(names) {
|
|
2752
|
+
return names.map((v) => `(${v} Int)`).join(" ");
|
|
2753
|
+
}
|
|
2754
|
+
function encodeColoured(plan, flat, initial, property, invariants, sinkPlaces) {
|
|
2007
2755
|
const P = flat.places.length;
|
|
2008
2756
|
const k = plan.k;
|
|
2009
|
-
const lay = buildLayout(
|
|
2010
|
-
const
|
|
2011
|
-
const
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
const
|
|
2757
|
+
const lay = buildLayout(plan, P);
|
|
2758
|
+
const nCols = lay.cur.length;
|
|
2759
|
+
const lines = [];
|
|
2760
|
+
lines.push("(set-logic HORN)");
|
|
2761
|
+
lines.push("");
|
|
2762
|
+
lines.push(`(declare-fun Reachable (${new Array(nCols).fill("Int").join(" ")}) Bool)`);
|
|
2763
|
+
lines.push("(declare-fun Error () Bool)");
|
|
2764
|
+
lines.push("");
|
|
2765
|
+
const init = [];
|
|
2018
2766
|
for (let i = 0; i < P; i++) {
|
|
2019
2767
|
if (plan.isColoured[i]) {
|
|
2020
|
-
for (let c = 0; c < k; c++)
|
|
2768
|
+
for (let c = 0; c < k; c++) init.push("0");
|
|
2021
2769
|
} else {
|
|
2022
|
-
|
|
2770
|
+
init.push(String(initial.tokens(flat.places[i])));
|
|
2023
2771
|
}
|
|
2024
2772
|
}
|
|
2025
|
-
|
|
2773
|
+
lines.push(`(assert (Reachable ${init.join(" ")}))`);
|
|
2774
|
+
lines.push("");
|
|
2026
2775
|
for (let ti = 0; ti < plan.classes.length; ti++) {
|
|
2027
2776
|
const cls = plan.classes[ti];
|
|
2028
2777
|
const ft = flat.transitions[ti];
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_consume_${cc}`, (enab, upd) => {
|
|
2072
|
-
uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
|
|
2073
|
-
const icol = lay.colCol[inputCol][cc];
|
|
2074
|
-
enab.push(lay.cur[icol].ge(1));
|
|
2075
|
-
upd.set(icol, lay.cur[icol].add(-1));
|
|
2076
|
-
for (const o of colouredOut) {
|
|
2077
|
-
const ocol = lay.colCol[o][cc];
|
|
2078
|
-
upd.set(ocol, lay.cur[ocol].add(1));
|
|
2079
|
-
}
|
|
2080
|
-
});
|
|
2081
|
-
}
|
|
2778
|
+
switch (cls.kind) {
|
|
2779
|
+
case "untouched":
|
|
2780
|
+
lines.push(encodeRule(plan, lay, invariants, (enab, upd) => uncolouredIncidence(lay, plan, ft, enab, upd)));
|
|
2781
|
+
break;
|
|
2782
|
+
case "mint":
|
|
2783
|
+
for (let c = 0; c < k; c++) {
|
|
2784
|
+
lines.push(encodeRule(plan, lay, invariants, (enab, upd) => {
|
|
2785
|
+
uncolouredIncidence(lay, plan, ft, enab, upd);
|
|
2786
|
+
for (const q of plan.coloured) enab.push(`(= ${lay.cur[lay.colCol[q][c]]} 0)`);
|
|
2787
|
+
for (const o of cls.colouredOut) {
|
|
2788
|
+
const col = lay.colCol[o][c];
|
|
2789
|
+
upd.push({ col, expr: `(+ ${lay.cur[col]} 1)` });
|
|
2790
|
+
}
|
|
2791
|
+
}));
|
|
2792
|
+
}
|
|
2793
|
+
break;
|
|
2794
|
+
case "join":
|
|
2795
|
+
for (let c = 0; c < k; c++) {
|
|
2796
|
+
lines.push(encodeRule(plan, lay, invariants, (enab, upd) => {
|
|
2797
|
+
uncolouredIncidence(lay, plan, ft, enab, upd);
|
|
2798
|
+
for (const ip of cls.colouredIn) {
|
|
2799
|
+
const col = lay.colCol[ip][c];
|
|
2800
|
+
enab.push(`(>= ${lay.cur[col]} 1)`);
|
|
2801
|
+
upd.push({ col, expr: `(- ${lay.cur[col]} 1)` });
|
|
2802
|
+
}
|
|
2803
|
+
}));
|
|
2804
|
+
}
|
|
2805
|
+
break;
|
|
2806
|
+
case "consume":
|
|
2807
|
+
for (let c = 0; c < k; c++) {
|
|
2808
|
+
lines.push(encodeRule(plan, lay, invariants, (enab, upd) => {
|
|
2809
|
+
uncolouredIncidence(lay, plan, ft, enab, upd);
|
|
2810
|
+
const icol = lay.colCol[cls.inputCol][c];
|
|
2811
|
+
enab.push(`(>= ${lay.cur[icol]} 1)`);
|
|
2812
|
+
upd.push({ col: icol, expr: `(- ${lay.cur[icol]} 1)` });
|
|
2813
|
+
for (const o of cls.colouredOut) {
|
|
2814
|
+
const ocol = lay.colCol[o][c];
|
|
2815
|
+
upd.push({ col: ocol, expr: `(+ ${lay.cur[ocol]} 1)` });
|
|
2816
|
+
}
|
|
2817
|
+
}));
|
|
2818
|
+
}
|
|
2819
|
+
break;
|
|
2082
2820
|
}
|
|
2083
2821
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2822
|
+
lines.push("");
|
|
2823
|
+
const error = encodeError(plan, lay, flat, property, sinkPlaces, injectionMap(flat));
|
|
2824
|
+
if (error == null) return null;
|
|
2825
|
+
lines.push(error);
|
|
2826
|
+
lines.push("");
|
|
2827
|
+
lines.push("(assert (not Error))");
|
|
2828
|
+
lines.push("(check-sat)");
|
|
2829
|
+
return { smt2: lines.join("\n"), placeCount: P };
|
|
2091
2830
|
}
|
|
2092
|
-
function
|
|
2831
|
+
function encodeRule(plan, lay, invariants, fill) {
|
|
2093
2832
|
const enab = [];
|
|
2094
|
-
const upd =
|
|
2833
|
+
const upd = [];
|
|
2095
2834
|
fill(enab, upd);
|
|
2096
|
-
const
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2835
|
+
const conditions = [`(Reachable ${lay.cur.join(" ")})`, ...enab];
|
|
2836
|
+
const changed = new Array(lay.cur.length).fill(null);
|
|
2837
|
+
for (const u of upd) changed[u.col] = u.expr;
|
|
2838
|
+
for (let col = 0; col < lay.cur.length; col++) {
|
|
2839
|
+
const expr = changed[col];
|
|
2840
|
+
if (expr != null) {
|
|
2841
|
+
conditions.push(`(= ${lay.nxt[col]} ${expr})`);
|
|
2842
|
+
conditions.push(`(>= ${lay.nxt[col]} 0)`);
|
|
2101
2843
|
} else {
|
|
2102
|
-
|
|
2844
|
+
conditions.push(`(= ${lay.nxt[col]} ${lay.cur[col]})`);
|
|
2103
2845
|
}
|
|
2104
2846
|
}
|
|
2105
2847
|
for (const inv of invariants) {
|
|
2106
|
-
const eq = liftedInvariant(
|
|
2107
|
-
if (eq)
|
|
2848
|
+
const eq = liftedInvariant(inv, plan, lay, lay.nxt);
|
|
2849
|
+
if (eq != null) conditions.push(eq);
|
|
2108
2850
|
}
|
|
2109
|
-
const body =
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2851
|
+
const body = `(and ${conditions.join("\n ")})`;
|
|
2852
|
+
return `(assert (forall (${quantified2([...lay.cur, ...lay.nxt])})
|
|
2853
|
+
(=> ${body}
|
|
2854
|
+
(Reachable ${lay.nxt.join(" ")}))))`;
|
|
2113
2855
|
}
|
|
2114
|
-
function uncolouredIncidence(
|
|
2856
|
+
function uncolouredIncidence(lay, plan, ft, enab, upd) {
|
|
2115
2857
|
const P = ft.preVector.length;
|
|
2116
2858
|
for (let i = 0; i < P; i++) {
|
|
2117
2859
|
if (plan.isColoured[i]) continue;
|
|
2118
2860
|
const col = lay.colUnc[i];
|
|
2119
2861
|
const pre = ft.preVector[i];
|
|
2120
|
-
if (pre > 0) enab.push(lay.cur[col]
|
|
2862
|
+
if (pre > 0) enab.push(`(>= ${lay.cur[col]} ${pre})`);
|
|
2121
2863
|
if (ft.resetPlaces.includes(i) || ft.consumeAll[i]) {
|
|
2122
|
-
upd.
|
|
2864
|
+
upd.push({ col, expr: String(ft.postVector[i]) });
|
|
2123
2865
|
} else {
|
|
2124
2866
|
const delta = ft.postVector[i] - ft.preVector[i];
|
|
2125
|
-
if (delta
|
|
2867
|
+
if (delta > 0) upd.push({ col, expr: `(+ ${lay.cur[col]} ${delta})` });
|
|
2868
|
+
else if (delta < 0) upd.push({ col, expr: `(- ${lay.cur[col]} ${-delta})` });
|
|
2126
2869
|
}
|
|
2127
2870
|
}
|
|
2128
|
-
for (const pid of ft.inhibitorPlaces) enab.push(lay.cur[lay.colUnc[pid]]
|
|
2129
|
-
for (const pid of ft.readPlaces) enab.push(lay.cur[lay.colUnc[pid]]
|
|
2871
|
+
for (const pid of ft.inhibitorPlaces) enab.push(`(= ${lay.cur[lay.colUnc[pid]]} 0)`);
|
|
2872
|
+
for (const pid of ft.readPlaces) enab.push(`(>= ${lay.cur[lay.colUnc[pid]]} 1)`);
|
|
2130
2873
|
}
|
|
2131
|
-
function aggregate(plan, lay, place,
|
|
2874
|
+
function aggregate(plan, lay, place, names) {
|
|
2132
2875
|
if (plan.isColoured[place]) {
|
|
2133
2876
|
const cols = lay.colCol[place];
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
return
|
|
2877
|
+
if (cols.length === 0) return "0";
|
|
2878
|
+
if (cols.length === 1) return names[cols[0]];
|
|
2879
|
+
return `(+ ${cols.map((c) => names[c]).join(" ")})`;
|
|
2137
2880
|
}
|
|
2138
|
-
return
|
|
2881
|
+
return names[lay.colUnc[place]];
|
|
2139
2882
|
}
|
|
2140
|
-
function liftedInvariant(
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
const agg = aggregate(plan, lay, i, vars);
|
|
2883
|
+
function liftedInvariant(inv, plan, lay, names) {
|
|
2884
|
+
const terms = [];
|
|
2885
|
+
for (const i of [...inv.support].sort((a, b) => a - b)) {
|
|
2886
|
+
const agg = aggregate(plan, lay, i, names);
|
|
2145
2887
|
const w = inv.weights[i];
|
|
2146
|
-
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
const violation = encodeViolation(ctx, plan, lay, flat, property, lay.cur, sinkPlaces);
|
|
2152
|
-
if (violation === null) return false;
|
|
2153
|
-
const reachBody = reachable.call(...lay.cur);
|
|
2154
|
-
const body = ctx.And(reachBody, violation);
|
|
2155
|
-
const head = error.call();
|
|
2156
|
-
const qRule = ctx.ForAll([...lay.cur], ctx.Implies(body, head));
|
|
2157
|
-
fp.addRule(qRule, "error");
|
|
2158
|
-
return true;
|
|
2888
|
+
terms.push(w === 1 ? agg : `(* ${w} ${agg})`);
|
|
2889
|
+
}
|
|
2890
|
+
if (terms.length === 0) return null;
|
|
2891
|
+
const sum = terms.length === 1 ? terms[0] : `(+ ${terms.join(" ")})`;
|
|
2892
|
+
return `(= ${sum} ${inv.constant})`;
|
|
2159
2893
|
}
|
|
2160
|
-
function
|
|
2894
|
+
function encodeError(plan, lay, flat, property, sinkPlaces, envInj) {
|
|
2895
|
+
const violation = encodeViolation(plan, lay, flat, property, sinkPlaces, envInj);
|
|
2896
|
+
if (violation == null) return null;
|
|
2897
|
+
return `(assert (forall (${quantified2(lay.cur)})
|
|
2898
|
+
(=> (and (Reachable ${lay.cur.join(" ")}) ${violation})
|
|
2899
|
+
Error)))`;
|
|
2900
|
+
}
|
|
2901
|
+
function encodeViolation(plan, lay, flat, property, sinkPlaces, envInj) {
|
|
2902
|
+
const anyPlacePresent = (places) => {
|
|
2903
|
+
const conds = indexOrdered(flat, places).map((pid) => `(>= ${aggregate(plan, lay, pid, lay.cur)} 1)`);
|
|
2904
|
+
return conds.length === 0 ? "false" : `(and ${conds.join(" ")})`;
|
|
2905
|
+
};
|
|
2161
2906
|
switch (property.type) {
|
|
2162
2907
|
case "place-bound":
|
|
2163
2908
|
case "branch-place-bound": {
|
|
2164
|
-
const
|
|
2165
|
-
if (
|
|
2166
|
-
return aggregate(plan, lay,
|
|
2167
|
-
}
|
|
2168
|
-
case "mutual-exclusion": {
|
|
2169
|
-
const i1 = flatNetIndexOf(flat, property.p1);
|
|
2170
|
-
const i2 = flatNetIndexOf(flat, property.p2);
|
|
2171
|
-
if (i1 < 0 || i2 < 0) return ctx.Bool.val(false);
|
|
2172
|
-
return ctx.And(aggregate(plan, lay, i1, cur).ge(1), aggregate(plan, lay, i2, cur).ge(1));
|
|
2173
|
-
}
|
|
2174
|
-
case "unreachable": {
|
|
2175
|
-
const conds = [];
|
|
2176
|
-
for (const place of property.places) {
|
|
2177
|
-
const idx = flatNetIndexOf(flat, place);
|
|
2178
|
-
if (idx >= 0) conds.push(aggregate(plan, lay, idx, cur).ge(1));
|
|
2179
|
-
}
|
|
2180
|
-
if (conds.length === 0) return ctx.Bool.val(false);
|
|
2181
|
-
return conds.length === 1 ? conds[0] : ctx.And(...conds);
|
|
2909
|
+
const pid = flat.placeIndex.get(property.place.name);
|
|
2910
|
+
if (pid == null) return null;
|
|
2911
|
+
return `(> ${aggregate(plan, lay, pid, lay.cur)} ${property.bound})`;
|
|
2182
2912
|
}
|
|
2913
|
+
case "mutual-exclusion":
|
|
2914
|
+
return anyPlacePresent([property.p1, property.p2]);
|
|
2915
|
+
case "unreachable":
|
|
2916
|
+
return anyPlacePresent(property.places);
|
|
2183
2917
|
case "deadlock-free":
|
|
2184
|
-
return encodeColouredDeadlock(
|
|
2918
|
+
return encodeColouredDeadlock(plan, lay, flat, sinkPlaces, envInj);
|
|
2185
2919
|
case "joined-or-dead-lettered": {
|
|
2186
|
-
const
|
|
2187
|
-
if (
|
|
2188
|
-
const deadlock = encodeColouredDeadlock(
|
|
2189
|
-
return
|
|
2920
|
+
const pid = flat.placeIndex.get(property.pending.name);
|
|
2921
|
+
if (pid == null) return null;
|
|
2922
|
+
const deadlock = encodeColouredDeadlock(plan, lay, flat, sinkPlaces, envInj);
|
|
2923
|
+
return `(and ${deadlock} (>= ${aggregate(plan, lay, pid, lay.cur)} 1))`;
|
|
2190
2924
|
}
|
|
2191
2925
|
}
|
|
2192
2926
|
}
|
|
2193
|
-
function
|
|
2194
|
-
if (xs.length === 0) return ctx.Bool.val(true);
|
|
2195
|
-
let r = xs[0];
|
|
2196
|
-
for (let i = 1; i < xs.length; i++) r = ctx.And(r, xs[i]);
|
|
2197
|
-
return r;
|
|
2198
|
-
}
|
|
2199
|
-
function orAll(ctx, xs) {
|
|
2200
|
-
if (xs.length === 0) return ctx.Bool.val(false);
|
|
2201
|
-
let r = xs[0];
|
|
2202
|
-
for (let i = 1; i < xs.length; i++) r = ctx.Or(r, xs[i]);
|
|
2203
|
-
return r;
|
|
2204
|
-
}
|
|
2205
|
-
function injectedEnvIndices2(flat) {
|
|
2206
|
-
const out = /* @__PURE__ */ new Map();
|
|
2207
|
-
for (const [name, bound] of flat.environmentInjection) {
|
|
2208
|
-
const idx = flat.placeIndex.get(name);
|
|
2209
|
-
if (idx != null) out.set(idx, bound);
|
|
2210
|
-
}
|
|
2211
|
-
return out;
|
|
2212
|
-
}
|
|
2213
|
-
function uncolouredDisable(ft, lay, plan, envInj) {
|
|
2214
|
-
const reasons = [];
|
|
2927
|
+
function uncolouredDisable(ft, lay, plan, envInj, reasons) {
|
|
2215
2928
|
let permanentlyDisabled = false;
|
|
2216
2929
|
const P = ft.preVector.length;
|
|
2217
2930
|
for (let i = 0; i < P; i++) {
|
|
2218
2931
|
if (plan.isColoured[i] || ft.preVector[i] === 0) continue;
|
|
2219
2932
|
if (envInj.has(i)) {
|
|
2220
2933
|
const bound = envInj.get(i);
|
|
2221
|
-
if (bound
|
|
2934
|
+
if (bound != null && ft.preVector[i] > bound) permanentlyDisabled = true;
|
|
2222
2935
|
continue;
|
|
2223
2936
|
}
|
|
2224
|
-
reasons.push(lay.cur[lay.colUnc[i]]
|
|
2225
|
-
}
|
|
2226
|
-
for (const inh of ft.inhibitorPlaces) {
|
|
2227
|
-
reasons.push(lay.cur[lay.colUnc[inh]].gt(0));
|
|
2937
|
+
reasons.push(`(< ${lay.cur[lay.colUnc[i]]} ${ft.preVector[i]})`);
|
|
2228
2938
|
}
|
|
2939
|
+
for (const inh of ft.inhibitorPlaces) reasons.push(`(> ${lay.cur[lay.colUnc[inh]]} 0)`);
|
|
2229
2940
|
for (const rd of ft.readPlaces) {
|
|
2230
2941
|
if (envInj.has(rd)) {
|
|
2231
2942
|
const bound = envInj.get(rd);
|
|
2232
|
-
if (bound
|
|
2943
|
+
if (bound != null && bound < 1) permanentlyDisabled = true;
|
|
2233
2944
|
continue;
|
|
2234
2945
|
}
|
|
2235
|
-
reasons.push(lay.cur[lay.colUnc[rd]]
|
|
2946
|
+
reasons.push(`(< ${lay.cur[lay.colUnc[rd]]} 1)`);
|
|
2236
2947
|
}
|
|
2237
|
-
return
|
|
2948
|
+
return permanentlyDisabled;
|
|
2238
2949
|
}
|
|
2239
|
-
function colouredDisabledTerm(
|
|
2950
|
+
function colouredDisabledTerm(cls, plan, lay) {
|
|
2240
2951
|
const k = plan.k;
|
|
2952
|
+
if (k === 0) {
|
|
2953
|
+
return cls.kind === "untouched" ? null : "true";
|
|
2954
|
+
}
|
|
2241
2955
|
switch (cls.kind) {
|
|
2242
2956
|
case "untouched":
|
|
2243
2957
|
return null;
|
|
2244
2958
|
case "mint": {
|
|
2245
2959
|
const perColour = [];
|
|
2246
2960
|
for (let c = 0; c < k; c++) {
|
|
2247
|
-
const present = plan.coloured.map((q) => lay.cur[lay.colCol[q][c]]
|
|
2248
|
-
perColour.push(
|
|
2961
|
+
const present = plan.coloured.map((q) => `(>= ${lay.cur[lay.colCol[q][c]]} 1)`);
|
|
2962
|
+
perColour.push(`(or ${present.join(" ")})`);
|
|
2249
2963
|
}
|
|
2250
|
-
return
|
|
2964
|
+
return `(and ${perColour.join(" ")})`;
|
|
2251
2965
|
}
|
|
2252
2966
|
case "join": {
|
|
2253
2967
|
const perColour = [];
|
|
2254
2968
|
for (let c = 0; c < k; c++) {
|
|
2255
|
-
const missing = cls.colouredIn.map((i) => lay.cur[lay.colCol[i][c]]
|
|
2256
|
-
perColour.push(
|
|
2969
|
+
const missing = cls.colouredIn.map((i) => `(= ${lay.cur[lay.colCol[i][c]]} 0)`);
|
|
2970
|
+
perColour.push(`(or ${missing.join(" ")})`);
|
|
2257
2971
|
}
|
|
2258
|
-
return
|
|
2972
|
+
return `(and ${perColour.join(" ")})`;
|
|
2259
2973
|
}
|
|
2260
2974
|
case "consume": {
|
|
2261
2975
|
const perColour = [];
|
|
2262
|
-
for (let c = 0; c < k; c++) {
|
|
2263
|
-
|
|
2264
|
-
}
|
|
2265
|
-
return andAll(ctx, perColour);
|
|
2976
|
+
for (let c = 0; c < k; c++) perColour.push(`(= ${lay.cur[lay.colCol[cls.inputCol][c]]} 0)`);
|
|
2977
|
+
return `(and ${perColour.join(" ")})`;
|
|
2266
2978
|
}
|
|
2267
2979
|
}
|
|
2268
2980
|
}
|
|
2269
|
-
function encodeColouredDeadlock(
|
|
2270
|
-
const envInj = injectedEnvIndices2(flat);
|
|
2981
|
+
function encodeColouredDeadlock(plan, lay, flat, sinkPlaces, envInj) {
|
|
2271
2982
|
const disabledConditions = [];
|
|
2272
2983
|
for (let ti = 0; ti < plan.classes.length; ti++) {
|
|
2273
2984
|
const cls = plan.classes[ti];
|
|
2274
2985
|
const ft = flat.transitions[ti];
|
|
2275
|
-
const
|
|
2986
|
+
const reasons = [];
|
|
2987
|
+
const permanentlyDisabled = uncolouredDisable(ft, lay, plan, envInj, reasons);
|
|
2276
2988
|
if (permanentlyDisabled) {
|
|
2277
|
-
disabledConditions.push(
|
|
2989
|
+
disabledConditions.push("true");
|
|
2278
2990
|
continue;
|
|
2279
2991
|
}
|
|
2280
|
-
const term = colouredDisabledTerm(
|
|
2281
|
-
if (term
|
|
2282
|
-
if (reasons.length === 0)
|
|
2283
|
-
|
|
2284
|
-
}
|
|
2285
|
-
disabledConditions.push(reasons.length === 1 ? reasons[0] : orAll(ctx, reasons));
|
|
2992
|
+
const term = colouredDisabledTerm(cls, plan, lay);
|
|
2993
|
+
if (term != null) reasons.push(term);
|
|
2994
|
+
if (reasons.length === 0) return "false";
|
|
2995
|
+
disabledConditions.push(reasons.length === 1 ? reasons[0] : `(or ${reasons.join(" ")})`);
|
|
2286
2996
|
}
|
|
2287
|
-
const
|
|
2288
|
-
|
|
2289
|
-
const idx = flatNetIndexOf(flat, sink);
|
|
2290
|
-
if (idx >= 0) sinkIndices.add(idx);
|
|
2291
|
-
}
|
|
2292
|
-
if (sinkIndices.size > 0) {
|
|
2293
|
-
const nonSink = [];
|
|
2294
|
-
for (let pid = 0; pid < flat.places.length; pid++) {
|
|
2295
|
-
if (sinkIndices.has(pid)) continue;
|
|
2296
|
-
nonSink.push(aggregate(plan, lay, pid, lay.cur).ge(1));
|
|
2297
|
-
}
|
|
2298
|
-
if (nonSink.length > 0) {
|
|
2299
|
-
disabledConditions.push(orAll(ctx, nonSink));
|
|
2300
|
-
}
|
|
2997
|
+
for (const pid of indexOrdered(flat, sinkPlaces)) {
|
|
2998
|
+
disabledConditions.push(`(= ${aggregate(plan, lay, pid, lay.cur)} 0)`);
|
|
2301
2999
|
}
|
|
2302
|
-
return
|
|
3000
|
+
return disabledConditions.length === 0 ? "true" : `(and ${disabledConditions.join(" ")})`;
|
|
2303
3001
|
}
|
|
2304
3002
|
|
|
2305
3003
|
// src/verification/analysis/name-fragment.ts
|
|
@@ -2366,9 +3064,9 @@ function classify(net, mode, carrierPlaces) {
|
|
|
2366
3064
|
role: (tn) => roles.get(tn) ?? { type: "ordinary" }
|
|
2367
3065
|
};
|
|
2368
3066
|
}
|
|
2369
|
-
function fixedRequiredCount(t,
|
|
3067
|
+
function fixedRequiredCount(t, placeName2) {
|
|
2370
3068
|
for (const spec of t.inputSpecs) {
|
|
2371
|
-
if (spec.place.name ===
|
|
3069
|
+
if (spec.place.name === placeName2) {
|
|
2372
3070
|
switch (spec.type) {
|
|
2373
3071
|
case "one":
|
|
2374
3072
|
return 1;
|
|
@@ -2478,13 +3176,21 @@ function compareNumberArrays(a, b) {
|
|
|
2478
3176
|
var NameStateClass = class {
|
|
2479
3177
|
base;
|
|
2480
3178
|
names;
|
|
2481
|
-
key
|
|
2482
|
-
|
|
3179
|
+
/** The symmetry-canonical name-partition key (the name layer's intern key). */
|
|
3180
|
+
nameKey;
|
|
3181
|
+
constructor(base, names, colouredOrder, nameKey) {
|
|
2483
3182
|
this.base = base;
|
|
2484
3183
|
this.names = names;
|
|
2485
|
-
this.
|
|
3184
|
+
this.nameKey = nameKey ?? names.canonicalKey(colouredOrder);
|
|
3185
|
+
}
|
|
3186
|
+
/** Full dedup key: the base key (marking + DBM zone) joined with the name key. */
|
|
3187
|
+
get key() {
|
|
3188
|
+
return `${baseKeyOf(this.base)}||${this.nameKey}`;
|
|
2486
3189
|
}
|
|
2487
3190
|
};
|
|
3191
|
+
function baseKeyOf(base) {
|
|
3192
|
+
return `${base.marking.toString()}|${base.firingDomain.toString()}`;
|
|
3193
|
+
}
|
|
2488
3194
|
|
|
2489
3195
|
// src/verification/analysis/name-state-class-graph.ts
|
|
2490
3196
|
var NameStateClassGraph = class _NameStateClassGraph {
|
|
@@ -2513,9 +3219,16 @@ var NameStateClassGraph = class _NameStateClassGraph {
|
|
|
2513
3219
|
}
|
|
2514
3220
|
const graph = new _NameStateClassGraph();
|
|
2515
3221
|
const base0 = initialStateClass(net, initialMarking, envPlaces, envMode);
|
|
2516
|
-
const
|
|
3222
|
+
const baseIntern = /* @__PURE__ */ new Map();
|
|
3223
|
+
const nameIntern = /* @__PURE__ */ new Map();
|
|
2517
3224
|
const indexOf = /* @__PURE__ */ new Map();
|
|
2518
|
-
|
|
3225
|
+
const b0 = internBase(baseIntern, base0);
|
|
3226
|
+
const n0 = internNames(nameIntern, new NameMarking(), fragment.colouredOrder);
|
|
3227
|
+
graph.pushClass(
|
|
3228
|
+
new NameStateClass(b0.base, n0.names, fragment.colouredOrder, n0.nameKey),
|
|
3229
|
+
classId(b0.id, n0.id),
|
|
3230
|
+
indexOf
|
|
3231
|
+
);
|
|
2519
3232
|
const sym = { next: 0 };
|
|
2520
3233
|
const queue = [0];
|
|
2521
3234
|
while (queue.length > 0) {
|
|
@@ -2544,12 +3257,18 @@ var NameStateClassGraph = class _NameStateClassGraph {
|
|
|
2544
3257
|
const baseSucc = computeSuccessor(net, current.base, vt, envPlaces, envMode);
|
|
2545
3258
|
if (baseSucc === null || baseSucc.isEmpty()) continue;
|
|
2546
3259
|
const nameSuccs = nameSuccessors(role, current.names, vt.outputPlaces, fragment, sym);
|
|
3260
|
+
const shared = internBase(baseIntern, baseSucc);
|
|
2547
3261
|
for (const nm of nameSuccs) {
|
|
2548
|
-
const
|
|
2549
|
-
|
|
3262
|
+
const sharedNames = internNames(nameIntern, nm, fragment.colouredOrder);
|
|
3263
|
+
const id = classId(shared.id, sharedNames.id);
|
|
3264
|
+
let toIdx = indexOf.get(id);
|
|
2550
3265
|
if (toIdx === void 0) {
|
|
2551
3266
|
toIdx = graph.classes.length;
|
|
2552
|
-
graph.pushClass(
|
|
3267
|
+
graph.pushClass(
|
|
3268
|
+
new NameStateClass(shared.base, sharedNames.names, fragment.colouredOrder, sharedNames.nameKey),
|
|
3269
|
+
id,
|
|
3270
|
+
indexOf
|
|
3271
|
+
);
|
|
2553
3272
|
queue.push(toIdx);
|
|
2554
3273
|
}
|
|
2555
3274
|
graph.addEdge(curIdx, toIdx, transition.name);
|
|
@@ -2559,17 +3278,38 @@ var NameStateClassGraph = class _NameStateClassGraph {
|
|
|
2559
3278
|
}
|
|
2560
3279
|
return graph;
|
|
2561
3280
|
}
|
|
2562
|
-
pushClass(c, indexOf) {
|
|
3281
|
+
pushClass(c, id, indexOf) {
|
|
2563
3282
|
const idx = this.classes.length;
|
|
2564
3283
|
this.classes.push(c);
|
|
2565
3284
|
this._successors.push([]);
|
|
2566
|
-
indexOf.set(
|
|
3285
|
+
indexOf.set(id, idx);
|
|
2567
3286
|
}
|
|
2568
3287
|
addEdge(from, to, name) {
|
|
2569
3288
|
this.edges.push({ from, to, transitionName: name });
|
|
2570
3289
|
this._successors[from].push(to);
|
|
2571
3290
|
}
|
|
2572
3291
|
};
|
|
3292
|
+
function classId(baseId, nameId) {
|
|
3293
|
+
return `${baseId}:${nameId}`;
|
|
3294
|
+
}
|
|
3295
|
+
function internBase(intern, base) {
|
|
3296
|
+
const key = `${baseKeyOf(base)}#${base.readyEarliest.join(",")}`;
|
|
3297
|
+
let entry = intern.get(key);
|
|
3298
|
+
if (entry === void 0) {
|
|
3299
|
+
entry = { id: intern.size, base };
|
|
3300
|
+
intern.set(key, entry);
|
|
3301
|
+
}
|
|
3302
|
+
return entry;
|
|
3303
|
+
}
|
|
3304
|
+
function internNames(intern, names, colouredOrder) {
|
|
3305
|
+
const nameKey = names.canonicalKey(colouredOrder);
|
|
3306
|
+
let entry = intern.get(nameKey);
|
|
3307
|
+
if (entry === void 0) {
|
|
3308
|
+
entry = { id: intern.size, names, nameKey };
|
|
3309
|
+
intern.set(nameKey, entry);
|
|
3310
|
+
}
|
|
3311
|
+
return entry;
|
|
3312
|
+
}
|
|
2573
3313
|
var READY_EPS = 1e-9;
|
|
2574
3314
|
function priorityDominated(l, idxL, enabled, readyEarliest, marking, names, fragment) {
|
|
2575
3315
|
return enabled.some(
|
|
@@ -2602,10 +3342,10 @@ function sharesConsumedInput(h, l, marking) {
|
|
|
2602
3342
|
}
|
|
2603
3343
|
return false;
|
|
2604
3344
|
}
|
|
2605
|
-
function consumedDemand(t,
|
|
3345
|
+
function consumedDemand(t, placeName2) {
|
|
2606
3346
|
let demand = 0;
|
|
2607
3347
|
for (const spec of t.inputSpecs) {
|
|
2608
|
-
if (spec.place.name ===
|
|
3348
|
+
if (spec.place.name === placeName2) demand += inputRequiredCount2(spec);
|
|
2609
3349
|
}
|
|
2610
3350
|
return demand;
|
|
2611
3351
|
}
|
|
@@ -2792,6 +3532,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2792
3532
|
constructor(net) {
|
|
2793
3533
|
this.net = net;
|
|
2794
3534
|
}
|
|
3535
|
+
net;
|
|
2795
3536
|
_initialMarking = MarkingState.empty();
|
|
2796
3537
|
_property = deadlockFree();
|
|
2797
3538
|
_environmentPlaces = /* @__PURE__ */ new Set();
|
|
@@ -2799,6 +3540,9 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2799
3540
|
_budgetPlaces = /* @__PURE__ */ new Set();
|
|
2800
3541
|
_environmentMode = alwaysAvailable();
|
|
2801
3542
|
_timeoutMs = 6e4;
|
|
3543
|
+
_certificateCheck = true;
|
|
3544
|
+
_counterexampleReplay = true;
|
|
3545
|
+
_semiflowInvariants = false;
|
|
2802
3546
|
_nuMaxClasses = 1e5;
|
|
2803
3547
|
_fragmentMode = "base";
|
|
2804
3548
|
_carrierPlaces = /* @__PURE__ */ new Set();
|
|
@@ -2852,6 +3596,58 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2852
3596
|
this._timeoutMs = ms;
|
|
2853
3597
|
return this;
|
|
2854
3598
|
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Enables/disables the independent IC3 certificate check (default: enabled).
|
|
3601
|
+
*
|
|
3602
|
+
* When a proven verdict comes from the IC3/Spacer path on the flat count
|
|
3603
|
+
* encoding, the synthesized inductive invariant is re-validated with a plain
|
|
3604
|
+
* solver against the UNSTRENGTHENED step relation — VC1 (init), VC2
|
|
3605
|
+
* (consecution), VC3 (safety) — so a Spacer or encoder defect cannot certify
|
|
3606
|
+
* a false PROVEN. A certificate that fails validation downgrades the verdict
|
|
3607
|
+
* to unknown. Structural proofs and the coloured ν-encoding are unaffected.
|
|
3608
|
+
*/
|
|
3609
|
+
certificateCheck(enabled) {
|
|
3610
|
+
this._certificateCheck = enabled;
|
|
3611
|
+
return this;
|
|
3612
|
+
}
|
|
3613
|
+
/**
|
|
3614
|
+
* Enables/disables abstract counterexample replay (default: enabled).
|
|
3615
|
+
*
|
|
3616
|
+
* When a violated verdict comes from the flat count encoding, the decoded
|
|
3617
|
+
* counterexample states (an order-free set — the derivation tree is walked in
|
|
3618
|
+
* traversal order, not firing order) are re-executed TS-side against the
|
|
3619
|
+
* abstract semantics the encoder emits (Lean's `fireA`, Basic.lean), searching
|
|
3620
|
+
* for a firing order from M₀ to a property-violating marking. See
|
|
3621
|
+
* `SmtVerificationResult.counterexampleConfirmed` for how each outcome lands.
|
|
3622
|
+
*/
|
|
3623
|
+
counterexampleReplay(enabled) {
|
|
3624
|
+
this._counterexampleReplay = enabled;
|
|
3625
|
+
return this;
|
|
3626
|
+
}
|
|
3627
|
+
/**
|
|
3628
|
+
* Also hands the validated **P-semiflows** to the encoders as invariants
|
|
3629
|
+
* (VER-007; default: disabled — the encoders then see only the null-space basis).
|
|
3630
|
+
*
|
|
3631
|
+
* Every validated semiflow is a conservation law in its own right (`y >= 0`,
|
|
3632
|
+
* `y·C = 0`, `y·M0` exact, zero weight on every reset / consume-all place), and
|
|
3633
|
+
* the Farkas enumeration returns the *minimal* laws of the net. The null-space
|
|
3634
|
+
* basis the encoders get by default is one basis of many: elimination hands back
|
|
3635
|
+
* mixed-sign rows (discarded as not semi-positive) or rows that fold a reset place
|
|
3636
|
+
* into a chain whose other combinations avoid it (dropped by the H1 guard). On a
|
|
3637
|
+
* net with a few reset arcs that can lose every law of the chains those arcs
|
|
3638
|
+
* touch, and without them IC3 has to rediscover the conservation of each chain —
|
|
3639
|
+
* on a ~100-place net it does not within any practical budget. With the semiflows
|
|
3640
|
+
* in, the same reachability-safety queries close in about a second.
|
|
3641
|
+
*
|
|
3642
|
+
* Soundness is unchanged: the semiflows pass the same exact re-validation as the
|
|
3643
|
+
* basis rows, the union is pure strengthening (`Semiflow.lean`,
|
|
3644
|
+
* `semiflow_union_sound`), and the certificate check re-proves the strengthened
|
|
3645
|
+
* invariant. Off by default so reports stay byte-equal.
|
|
3646
|
+
*/
|
|
3647
|
+
semiflowInvariants(enabled) {
|
|
3648
|
+
this._semiflowInvariants = enabled;
|
|
3649
|
+
return this;
|
|
3650
|
+
}
|
|
2855
3651
|
/**
|
|
2856
3652
|
* Sets the class-count cap for the ν-aware state-class-graph analysis (NU-050,
|
|
2857
3653
|
* Route B). When the symbolic name-aware graph would exceed this, the analysis
|
|
@@ -2906,6 +3702,62 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2906
3702
|
this._prioritySemantics = semantics;
|
|
2907
3703
|
return this;
|
|
2908
3704
|
}
|
|
3705
|
+
/**
|
|
3706
|
+
* The SMT-LIB2 scripts {@link verify} would send to z3 for this configuration,
|
|
3707
|
+
* without running a solver (VER-013 AC1): the HORN query (flat, or name-coloured
|
|
3708
|
+
* when a declared budget puts the net on Route A's exact encoding) and, for the
|
|
3709
|
+
* flat encoding, the certificate-check script built around
|
|
3710
|
+
* {@link placeholderCertificate}. This is what the cross-language golden tests diff
|
|
3711
|
+
* byte for byte. Route B, the structural pre-check and the unresolved-place
|
|
3712
|
+
* refusal are bypassed: it is what Route A encodes.
|
|
3713
|
+
*/
|
|
3714
|
+
encodeScripts() {
|
|
3715
|
+
requireOutputProducingActions(this.net);
|
|
3716
|
+
const hasMatch = [...this.net.transitions].some((t) => t.matchSpec !== null);
|
|
3717
|
+
const nuBounded = this._budgetPlaces.size > 0;
|
|
3718
|
+
const flatNet = flatten(this.net, this._environmentPlaces, this._environmentMode);
|
|
3719
|
+
const matrix = IncidenceMatrix.from(flatNet);
|
|
3720
|
+
const { valid: basis } = validateInvariantsExact(
|
|
3721
|
+
matrix,
|
|
3722
|
+
computePInvariants(matrix, flatNet, this._initialMarking),
|
|
3723
|
+
flatNet,
|
|
3724
|
+
this._initialMarking
|
|
3725
|
+
);
|
|
3726
|
+
const { valid: semiflows } = validateInvariantsExact(
|
|
3727
|
+
matrix,
|
|
3728
|
+
computePSemiflows(matrix, flatNet, this._initialMarking),
|
|
3729
|
+
flatNet,
|
|
3730
|
+
this._initialMarking
|
|
3731
|
+
);
|
|
3732
|
+
let invariants = basis;
|
|
3733
|
+
if (this._semiflowInvariants) invariants = strengthenWithSemiflows(basis, semiflows).invariants;
|
|
3734
|
+
invariants = canonicalInvariantOrder(invariants);
|
|
3735
|
+
if (hasMatch && nuBounded) {
|
|
3736
|
+
const plan = buildColouredPlan(
|
|
3737
|
+
this.net,
|
|
3738
|
+
flatNet,
|
|
3739
|
+
this._initialMarking,
|
|
3740
|
+
this._budgetPlaces,
|
|
3741
|
+
this._fragmentMode,
|
|
3742
|
+
this._carrierPlaces,
|
|
3743
|
+
semiflows
|
|
3744
|
+
);
|
|
3745
|
+
if (plan != null) {
|
|
3746
|
+
const coloured = encodeColoured(plan, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
|
|
3747
|
+
if (coloured != null) return { horn: coloured.smt2, certificate: null, coloured: true };
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
const horn = encode(flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces, this._counterexampleReplay).smt2;
|
|
3751
|
+
const certificate = vcScript(
|
|
3752
|
+
placeholderCertificate(flatNet.places.length),
|
|
3753
|
+
flatNet,
|
|
3754
|
+
this._initialMarking,
|
|
3755
|
+
this._property,
|
|
3756
|
+
this._sinkPlaces,
|
|
3757
|
+
invariants
|
|
3758
|
+
);
|
|
3759
|
+
return { horn, certificate, coloured: false };
|
|
3760
|
+
}
|
|
2909
3761
|
/**
|
|
2910
3762
|
* Runs the verification pipeline.
|
|
2911
3763
|
*
|
|
@@ -2998,6 +3850,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2998
3850
|
report.push("=== RESULT ===\n");
|
|
2999
3851
|
report.push("PROVEN (structural): Deadlock-freedom verified by Commoner's theorem.");
|
|
3000
3852
|
report.push(" All siphons contain initially marked traps.");
|
|
3853
|
+
report.push(" Certificate check: not applicable (structural proof)");
|
|
3001
3854
|
return buildResult(
|
|
3002
3855
|
{ type: "proven", method: "structural", inductiveInvariant: null },
|
|
3003
3856
|
report.join("\n"),
|
|
@@ -3011,14 +3864,43 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3011
3864
|
}
|
|
3012
3865
|
report.push("Phase 3: Computing P-invariants...");
|
|
3013
3866
|
const matrix = IncidenceMatrix.from(flatNet);
|
|
3014
|
-
const
|
|
3015
|
-
|
|
3016
|
-
|
|
3867
|
+
const { valid: basisInvariants, dropped: droppedInvariants } = validateInvariantsExact(
|
|
3868
|
+
matrix,
|
|
3869
|
+
computePInvariants(matrix, flatNet, this._initialMarking),
|
|
3870
|
+
flatNet,
|
|
3871
|
+
this._initialMarking
|
|
3872
|
+
);
|
|
3873
|
+
const { valid: semiflows, dropped: droppedSemiflows } = validateInvariantsExact(
|
|
3874
|
+
matrix,
|
|
3875
|
+
computePSemiflows(matrix, flatNet, this._initialMarking),
|
|
3876
|
+
flatNet,
|
|
3877
|
+
this._initialMarking
|
|
3878
|
+
);
|
|
3879
|
+
report.push(` Found: ${basisInvariants.length} P-invariant(s)`);
|
|
3880
|
+
let invariants = basisInvariants;
|
|
3881
|
+
if (this._semiflowInvariants) {
|
|
3882
|
+
const { invariants: strengthened, added } = strengthenWithSemiflows(basisInvariants, semiflows);
|
|
3883
|
+
invariants = strengthened;
|
|
3884
|
+
report.push(` Semiflows encoded as invariants: ${added}`);
|
|
3885
|
+
}
|
|
3886
|
+
invariants = canonicalInvariantOrder(invariants);
|
|
3017
3887
|
const structurallyBounded = isCoveredByInvariants(invariants, flatNet.places.length);
|
|
3018
3888
|
report.push(` Structurally bounded: ${structurallyBounded ? "YES" : "NO"}`);
|
|
3019
3889
|
for (const inv of invariants) {
|
|
3020
3890
|
report.push(` ${formatInvariant(inv, flatNet)}`);
|
|
3021
3891
|
}
|
|
3892
|
+
for (const { invariant, reason } of droppedInvariants) {
|
|
3893
|
+
report.push(` Dropped invariant: ${formatInvariant(invariant, flatNet)} - ${reason}`);
|
|
3894
|
+
}
|
|
3895
|
+
if (droppedInvariants.length > 0) {
|
|
3896
|
+
report.push(` Dropped: ${droppedInvariants.length} invariant(s) failed the exact re-check`);
|
|
3897
|
+
}
|
|
3898
|
+
for (const { invariant, reason } of droppedSemiflows) {
|
|
3899
|
+
report.push(` Dropped semiflow: ${formatInvariant(invariant, flatNet)} - ${reason}`);
|
|
3900
|
+
}
|
|
3901
|
+
if (droppedSemiflows.length > 0) {
|
|
3902
|
+
report.push(` Dropped: ${droppedSemiflows.length} semiflow(s) failed the exact re-check`);
|
|
3903
|
+
}
|
|
3022
3904
|
report.push("");
|
|
3023
3905
|
report.push("Phase 4: IC3/PDR verification via Z3 Spacer...");
|
|
3024
3906
|
const colouredPlan = hasMatch && nuBounded ? buildColouredPlan(
|
|
@@ -3030,173 +3912,208 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3030
3912
|
this._carrierPlaces,
|
|
3031
3913
|
semiflows
|
|
3032
3914
|
) : null;
|
|
3033
|
-
|
|
3915
|
+
const stats = {
|
|
3916
|
+
places: flatNet.places.length,
|
|
3917
|
+
transitions: flatNet.transitions.length,
|
|
3918
|
+
invariantsFound: invariants.length,
|
|
3919
|
+
structuralResult: structResultStr
|
|
3920
|
+
};
|
|
3921
|
+
let solver;
|
|
3034
3922
|
try {
|
|
3035
|
-
|
|
3923
|
+
solver = resolveZ3();
|
|
3036
3924
|
} catch (e) {
|
|
3037
|
-
|
|
3925
|
+
const reason = e instanceof Z3Unavailable ? e.message : String(e?.message ?? e);
|
|
3926
|
+
report.push(` Solver: z3 unavailable (${reason})`);
|
|
3927
|
+
report.push(` Status: UNKNOWN (${reason})
|
|
3038
3928
|
`);
|
|
3039
3929
|
report.push("=== RESULT ===\n");
|
|
3040
|
-
report.push(`UNKNOWN:
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3930
|
+
report.push(`UNKNOWN: Could not determine ${propDesc}`);
|
|
3931
|
+
report.push(` Reason: ${reason}`);
|
|
3932
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
3933
|
+
}
|
|
3934
|
+
report.push(` Solver: z3 ${formatZ3Version(solver.version)}`);
|
|
3935
|
+
let encoding;
|
|
3936
|
+
if (colouredPlan != null) {
|
|
3937
|
+
report.push(
|
|
3938
|
+
` \u03BD-encoding: name-coloured (exact within budget k=${colouredPlan.k}; ${colouredPlan.coloured.length} coloured place(s))`
|
|
3050
3939
|
);
|
|
3940
|
+
const coloured = encodeColoured(colouredPlan, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
|
|
3941
|
+
if (coloured == null) {
|
|
3942
|
+
const reason = "property names a place that does not resolve in the net; refusing to certify (the encoding would be vacuously proven)";
|
|
3943
|
+
report.push(" Status: UNKNOWN (unresolved property place)\n");
|
|
3944
|
+
report.push("=== RESULT ===\n");
|
|
3945
|
+
report.push(`UNKNOWN: ${reason}`);
|
|
3946
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
3947
|
+
}
|
|
3948
|
+
encoding = coloured;
|
|
3949
|
+
} else {
|
|
3950
|
+
const unresolved = unresolvedPropertyPlace(flatNet, this._property);
|
|
3951
|
+
if (unresolved != null) {
|
|
3952
|
+
const reason = `property names a place that does not resolve in the net ('${unresolved}'); refusing to certify (the encoding would be vacuously proven)`;
|
|
3953
|
+
report.push(" Status: UNKNOWN (unresolved property place)\n");
|
|
3954
|
+
report.push("=== RESULT ===\n");
|
|
3955
|
+
report.push(`UNKNOWN: ${reason}`);
|
|
3956
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
3957
|
+
}
|
|
3958
|
+
encoding = encode(flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces, this._counterexampleReplay);
|
|
3051
3959
|
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3960
|
+
const queryResult = await runZ3Spacer(
|
|
3961
|
+
solver,
|
|
3962
|
+
this._timeoutMs,
|
|
3963
|
+
encoding.smt2,
|
|
3964
|
+
colouredPlan != null ? "horn-coloured" : "horn"
|
|
3965
|
+
);
|
|
3966
|
+
switch (queryResult.type) {
|
|
3967
|
+
case "proven": {
|
|
3968
|
+
if (this._environmentPlaces.size > 0 && this._environmentMode.type === "ignore") {
|
|
3969
|
+
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";
|
|
3970
|
+
report.push(` Status: UNSAT, but vacuous under ignore mode
|
|
3971
|
+
`);
|
|
3062
3972
|
report.push("=== RESULT ===\n");
|
|
3063
3973
|
report.push(`UNKNOWN: ${reason}`);
|
|
3064
|
-
return buildResult(
|
|
3065
|
-
|
|
3066
|
-
|
|
3974
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
3975
|
+
}
|
|
3976
|
+
report.push(" Status: UNSAT (property holds)");
|
|
3977
|
+
if (colouredPlan != null) {
|
|
3978
|
+
report.push(" Certificate check: not applicable (name-coloured encoding)");
|
|
3979
|
+
} else if (!this._certificateCheck) {
|
|
3980
|
+
report.push(" Certificate check: not applicable (disabled)");
|
|
3981
|
+
} else {
|
|
3982
|
+
const certificate = await checkCertificate(
|
|
3983
|
+
queryResult.invariantFormula,
|
|
3984
|
+
flatNet,
|
|
3985
|
+
this._initialMarking,
|
|
3986
|
+
this._property,
|
|
3067
3987
|
invariants,
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
performance.now() - start,
|
|
3072
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3988
|
+
this._sinkPlaces,
|
|
3989
|
+
solver,
|
|
3990
|
+
this._timeoutMs
|
|
3073
3991
|
);
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
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";
|
|
3083
|
-
report.push(` Status: UNSAT, but vacuous under ignore mode
|
|
3084
|
-
`);
|
|
3992
|
+
const reason = certificateDowngradeReason(certificate);
|
|
3993
|
+
if (reason != null) {
|
|
3994
|
+
report.push(" Certificate check: FAILED");
|
|
3995
|
+
if (certificate.type !== "passed" && certificate.invariant != null) {
|
|
3996
|
+
report.push(" Uncertified invariant:");
|
|
3997
|
+
for (const line of certificate.invariant.split("\n")) report.push(` ${line}`);
|
|
3998
|
+
}
|
|
3999
|
+
report.push("");
|
|
3085
4000
|
report.push("=== RESULT ===\n");
|
|
3086
4001
|
report.push(`UNKNOWN: ${reason}`);
|
|
4002
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
4003
|
+
}
|
|
4004
|
+
report.push(" Certificate check: PASSED (init, consecution, safety)");
|
|
4005
|
+
}
|
|
4006
|
+
report.push("");
|
|
4007
|
+
const formula = queryResult.invariantFormula;
|
|
4008
|
+
const discoveredInvariants = formula != null ? [formula] : [];
|
|
4009
|
+
if (formula != null) {
|
|
4010
|
+
report.push("Phase 5: Inductive invariant (discovered by IC3)");
|
|
4011
|
+
report.push(" Spacer synthesized:");
|
|
4012
|
+
for (const line of formula.split("\n")) report.push(` ${line}`);
|
|
4013
|
+
report.push(" This formula is INDUCTIVE: preserved by all transitions.");
|
|
4014
|
+
report.push("");
|
|
4015
|
+
}
|
|
4016
|
+
report.push("=== RESULT ===\n");
|
|
4017
|
+
report.push(`PROVEN (IC3/PDR): ${propDesc}`);
|
|
4018
|
+
report.push(" Z3 Spacer proved no reachable state violates the property.");
|
|
4019
|
+
report.push(" NOTE: Verification ignores timing constraints.");
|
|
4020
|
+
report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
|
|
4021
|
+
return this.applyNuGuard(buildResult(
|
|
4022
|
+
{ type: "proven", method: "IC3/PDR", inductiveInvariant: formula },
|
|
4023
|
+
report.join("\n"),
|
|
4024
|
+
invariants,
|
|
4025
|
+
discoveredInvariants,
|
|
4026
|
+
[],
|
|
4027
|
+
[],
|
|
4028
|
+
performance.now() - start,
|
|
4029
|
+
stats
|
|
4030
|
+
), hasMatch, nuBounded, colouredPlan != null);
|
|
4031
|
+
}
|
|
4032
|
+
case "violated": {
|
|
4033
|
+
report.push(" Status: SAT (counterexample found)\n");
|
|
4034
|
+
const decoded = decode(queryResult.answer, flatNet);
|
|
4035
|
+
if (decoded.note != null) report.push(` Counterexample decoding: ${decoded.note}`);
|
|
4036
|
+
let confirmed = null;
|
|
4037
|
+
let trace = [...decoded.states];
|
|
4038
|
+
let transitions = [];
|
|
4039
|
+
let replayed = false;
|
|
4040
|
+
if (colouredPlan == null && this._counterexampleReplay) {
|
|
4041
|
+
const assessment = assessCounterexample(
|
|
4042
|
+
flatNet,
|
|
4043
|
+
this._initialMarking,
|
|
4044
|
+
decoded.states,
|
|
4045
|
+
this._property,
|
|
4046
|
+
this._sinkPlaces
|
|
4047
|
+
);
|
|
4048
|
+
if (assessment.kind === "confirmed") {
|
|
4049
|
+
confirmed = true;
|
|
4050
|
+
replayed = true;
|
|
4051
|
+
trace = assessment.trace;
|
|
4052
|
+
transitions = assessment.firings;
|
|
4053
|
+
report.push(" Counterexample replay: CONFIRMED (abstract chain M0 -> bad re-executed)");
|
|
4054
|
+
} else if (assessment.kind === "unconfirmed") {
|
|
4055
|
+
confirmed = false;
|
|
4056
|
+
report.push(` Counterexample replay: UNCONFIRMED (${assessment.note})`);
|
|
4057
|
+
report.push(" The verdict rests on Spacer's answer.");
|
|
4058
|
+
} else {
|
|
4059
|
+
report.push(" Counterexample replay: FAILED");
|
|
4060
|
+
report.push(` Decoded states (order-free set, ${decoded.states.size}):`);
|
|
4061
|
+
for (const m of decoded.states) report.push(` ${m}`);
|
|
4062
|
+
report.push(` Raw Z3 answer: ${truncate(queryResult.answer, 2e3)}`);
|
|
4063
|
+
report.push("");
|
|
4064
|
+
report.push("=== RESULT ===\n");
|
|
4065
|
+
report.push(`UNKNOWN: ${assessment.reason}`);
|
|
3087
4066
|
return buildResult(
|
|
3088
|
-
{ type: "unknown", reason },
|
|
4067
|
+
{ type: "unknown", reason: assessment.reason },
|
|
3089
4068
|
report.join("\n"),
|
|
3090
4069
|
invariants,
|
|
3091
4070
|
[],
|
|
3092
4071
|
[],
|
|
3093
4072
|
[],
|
|
3094
4073
|
performance.now() - start,
|
|
3095
|
-
|
|
4074
|
+
stats,
|
|
4075
|
+
false
|
|
3096
4076
|
);
|
|
3097
4077
|
}
|
|
3098
|
-
report.push(" Status: UNSAT (property holds)\n");
|
|
3099
|
-
const discoveredInvariants = [];
|
|
3100
|
-
if (queryResult.invariantFormula != null) {
|
|
3101
|
-
discoveredInvariants.push(substituteNames(queryResult.invariantFormula, flatNet));
|
|
3102
|
-
}
|
|
3103
|
-
for (const level of queryResult.levelInvariants) {
|
|
3104
|
-
discoveredInvariants.push(substituteNames(level, flatNet));
|
|
3105
|
-
}
|
|
3106
|
-
if (discoveredInvariants.length > 0) {
|
|
3107
|
-
report.push("Phase 5: Inductive invariant (discovered by IC3)");
|
|
3108
|
-
report.push(` Spacer synthesized: ${discoveredInvariants[0]}`);
|
|
3109
|
-
report.push(" This formula is INDUCTIVE: preserved by all transitions.");
|
|
3110
|
-
if (discoveredInvariants.length > 1) {
|
|
3111
|
-
report.push(" Per-level clauses:");
|
|
3112
|
-
for (let i = 1; i < discoveredInvariants.length; i++) {
|
|
3113
|
-
report.push(` ${discoveredInvariants[i]}`);
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3116
|
-
report.push("");
|
|
3117
|
-
}
|
|
3118
|
-
report.push("=== RESULT ===\n");
|
|
3119
|
-
report.push(`PROVEN (IC3/PDR): ${propDesc}`);
|
|
3120
|
-
report.push(" Z3 Spacer proved no reachable state violates the property.");
|
|
3121
|
-
report.push(" NOTE: Verification ignores timing constraints.");
|
|
3122
|
-
report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
|
|
3123
|
-
return this.applyNuGuard(buildResult(
|
|
3124
|
-
{
|
|
3125
|
-
type: "proven",
|
|
3126
|
-
method: "IC3/PDR",
|
|
3127
|
-
inductiveInvariant: queryResult.invariantFormula != null ? substituteNames(queryResult.invariantFormula, flatNet) : null
|
|
3128
|
-
},
|
|
3129
|
-
report.join("\n"),
|
|
3130
|
-
invariants,
|
|
3131
|
-
discoveredInvariants,
|
|
3132
|
-
[],
|
|
3133
|
-
[],
|
|
3134
|
-
performance.now() - start,
|
|
3135
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3136
|
-
), hasMatch, nuBounded, colouredPlan != null);
|
|
3137
|
-
}
|
|
3138
|
-
case "violated": {
|
|
3139
|
-
report.push(" Status: SAT (counterexample found)\n");
|
|
3140
|
-
const decoded = decode(runner.ctx, queryResult.answer, flatNet);
|
|
3141
|
-
report.push("=== RESULT ===\n");
|
|
3142
|
-
report.push(`VIOLATED: ${propDesc}`);
|
|
3143
|
-
if (decoded.trace.length > 0) {
|
|
3144
|
-
report.push(` Counterexample trace (${decoded.trace.length} states):`);
|
|
3145
|
-
for (let i = 0; i < decoded.trace.length; i++) {
|
|
3146
|
-
report.push(` ${i}: ${decoded.trace[i]}`);
|
|
3147
|
-
}
|
|
3148
|
-
}
|
|
3149
|
-
if (decoded.transitions.length > 0) {
|
|
3150
|
-
report.push(` Firing sequence: ${decoded.transitions.join(" -> ")}`);
|
|
3151
|
-
}
|
|
3152
|
-
report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
|
|
3153
|
-
report.push(" It may be spurious if timing constraints prevent this sequence.");
|
|
3154
|
-
return this.applyNuGuard(buildResult(
|
|
3155
|
-
{ type: "violated" },
|
|
3156
|
-
report.join("\n"),
|
|
3157
|
-
invariants,
|
|
3158
|
-
[],
|
|
3159
|
-
decoded.trace,
|
|
3160
|
-
decoded.transitions,
|
|
3161
|
-
performance.now() - start,
|
|
3162
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3163
|
-
), hasMatch, nuBounded, colouredPlan != null);
|
|
3164
4078
|
}
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
report.push("
|
|
3169
|
-
report.push(`
|
|
3170
|
-
report.push(` Reason: ${queryResult.reason}`);
|
|
3171
|
-
return buildResult(
|
|
3172
|
-
{ type: "unknown", reason: queryResult.reason },
|
|
3173
|
-
report.join("\n"),
|
|
3174
|
-
invariants,
|
|
3175
|
-
[],
|
|
3176
|
-
[],
|
|
3177
|
-
[],
|
|
3178
|
-
performance.now() - start,
|
|
3179
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3180
|
-
);
|
|
4079
|
+
report.push("=== RESULT ===\n");
|
|
4080
|
+
report.push(`VIOLATED: ${propDesc}`);
|
|
4081
|
+
if (trace.length > 0) {
|
|
4082
|
+
report.push(` Counterexample trace (${replayed ? "replay order, " : "proof order, "}${trace.length} states):`);
|
|
4083
|
+
for (let i = 0; i < trace.length; i++) report.push(` ${i}: ${trace[i]}`);
|
|
3181
4084
|
}
|
|
4085
|
+
if (transitions.length > 0) report.push(` Firing sequence: ${transitions.join(" -> ")}`);
|
|
4086
|
+
report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
|
|
4087
|
+
report.push(" It may be spurious if timing constraints prevent this sequence.");
|
|
4088
|
+
return this.applyNuGuard(buildResult(
|
|
4089
|
+
{ type: "violated" },
|
|
4090
|
+
report.join("\n"),
|
|
4091
|
+
invariants,
|
|
4092
|
+
[],
|
|
4093
|
+
trace,
|
|
4094
|
+
transitions,
|
|
4095
|
+
performance.now() - start,
|
|
4096
|
+
stats,
|
|
4097
|
+
confirmed
|
|
4098
|
+
), hasMatch, nuBounded, colouredPlan != null);
|
|
3182
4099
|
}
|
|
3183
|
-
|
|
3184
|
-
|
|
4100
|
+
case "unknown": {
|
|
4101
|
+
report.push(` Status: UNKNOWN (${queryResult.reason})
|
|
3185
4102
|
`);
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
4103
|
+
report.push("=== RESULT ===\n");
|
|
4104
|
+
report.push(`UNKNOWN: Could not determine ${propDesc}`);
|
|
4105
|
+
report.push(` Reason: ${queryResult.reason}`);
|
|
4106
|
+
return buildResult(
|
|
4107
|
+
{ type: "unknown", reason: queryResult.reason },
|
|
4108
|
+
report.join("\n"),
|
|
4109
|
+
invariants,
|
|
4110
|
+
[],
|
|
4111
|
+
[],
|
|
4112
|
+
[],
|
|
4113
|
+
performance.now() - start,
|
|
4114
|
+
stats
|
|
4115
|
+
);
|
|
4116
|
+
}
|
|
3200
4117
|
}
|
|
3201
4118
|
}
|
|
3202
4119
|
/**
|
|
@@ -3252,6 +4169,57 @@ function isReachabilitySafety(property) {
|
|
|
3252
4169
|
return false;
|
|
3253
4170
|
}
|
|
3254
4171
|
}
|
|
4172
|
+
function assessCounterexample(flatNet, initialMarking, decodedStates, property, sinkPlaces) {
|
|
4173
|
+
if (decodedStates.size === 0) {
|
|
4174
|
+
return {
|
|
4175
|
+
kind: "unconfirmed",
|
|
4176
|
+
note: "no counterexample states could be decoded from the Spacer answer, so the abstract replay could not run"
|
|
4177
|
+
};
|
|
4178
|
+
}
|
|
4179
|
+
let outcome;
|
|
4180
|
+
try {
|
|
4181
|
+
outcome = replayCounterexample(
|
|
4182
|
+
flatNet,
|
|
4183
|
+
vectorize(initialMarking, flatNet),
|
|
4184
|
+
[...decodedStates].map((m) => vectorize(m, flatNet)),
|
|
4185
|
+
property,
|
|
4186
|
+
sinkPlaces
|
|
4187
|
+
);
|
|
4188
|
+
} catch (e) {
|
|
4189
|
+
outcome = { kind: "exhausted", reason: `replay threw: ${e?.message ?? e}`, nodesExplored: 0 };
|
|
4190
|
+
}
|
|
4191
|
+
switch (outcome.kind) {
|
|
4192
|
+
case "confirmed":
|
|
4193
|
+
return {
|
|
4194
|
+
kind: "confirmed",
|
|
4195
|
+
trace: outcome.states.map((s) => toMarkingState(s, flatNet)),
|
|
4196
|
+
firings: outcome.steps.map(stepName)
|
|
4197
|
+
};
|
|
4198
|
+
case "exhausted":
|
|
4199
|
+
return { kind: "unconfirmed", note: `abstract replay did not complete: ${outcome.reason}` };
|
|
4200
|
+
case "no-chain":
|
|
4201
|
+
return {
|
|
4202
|
+
kind: "downgraded",
|
|
4203
|
+
reason: "counterexample replay found no firing chain to the violation under the abstract semantics, so VIOLATED is withheld"
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
function certificateDowngradeReason(outcome) {
|
|
4208
|
+
switch (outcome.type) {
|
|
4209
|
+
case "passed":
|
|
4210
|
+
return null;
|
|
4211
|
+
case "failed":
|
|
4212
|
+
return `certificate check failed: ${outcome.vc} was not UNSAT - ${outcome.detail}; the IC3 certificate could not be independently re-validated against the unstrengthened step relation, so PROVEN is withheld`;
|
|
4213
|
+
case "unavailable":
|
|
4214
|
+
return `certificate check could not run: ${outcome.reason}; PROVEN is withheld without an independently validated certificate`;
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
function placeholderCertificate(placeCount) {
|
|
4218
|
+
const params = [];
|
|
4219
|
+
for (let i = 0; i < placeCount; i++) params.push(`(x!${i} Int)`);
|
|
4220
|
+
return `(define-fun Reachable (${params.join(" ")}) Bool
|
|
4221
|
+
true)`;
|
|
4222
|
+
}
|
|
3255
4223
|
function downgradeToUnknown(result, reason) {
|
|
3256
4224
|
return {
|
|
3257
4225
|
...result,
|
|
@@ -3261,14 +4229,34 @@ Downgraded to UNKNOWN: ${reason}
|
|
|
3261
4229
|
`,
|
|
3262
4230
|
discoveredInvariants: [],
|
|
3263
4231
|
counterexampleTrace: [],
|
|
3264
|
-
counterexampleTransitions: []
|
|
4232
|
+
counterexampleTransitions: [],
|
|
4233
|
+
counterexampleConfirmed: null
|
|
3265
4234
|
};
|
|
3266
4235
|
}
|
|
3267
|
-
function
|
|
3268
|
-
|
|
3269
|
-
|
|
4236
|
+
function truncate(s, max) {
|
|
4237
|
+
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (${s.length - max} chars truncated)`;
|
|
4238
|
+
}
|
|
4239
|
+
function unresolvedPropertyPlace(flatNet, property) {
|
|
4240
|
+
const named = (() => {
|
|
4241
|
+
switch (property.type) {
|
|
4242
|
+
case "deadlock-free":
|
|
4243
|
+
return [];
|
|
4244
|
+
case "mutual-exclusion":
|
|
4245
|
+
return [property.p1, property.p2];
|
|
4246
|
+
case "place-bound":
|
|
4247
|
+
return [property.place];
|
|
4248
|
+
case "branch-place-bound":
|
|
4249
|
+
return [property.place];
|
|
4250
|
+
case "unreachable":
|
|
4251
|
+
return [...property.places];
|
|
4252
|
+
case "joined-or-dead-lettered":
|
|
4253
|
+
return [property.pending];
|
|
4254
|
+
}
|
|
4255
|
+
})();
|
|
4256
|
+
for (const place of named) {
|
|
4257
|
+
if (!flatNet.placeIndex.has(place.name)) return place.name;
|
|
3270
4258
|
}
|
|
3271
|
-
return
|
|
4259
|
+
return null;
|
|
3272
4260
|
}
|
|
3273
4261
|
function formatInvariant(inv, flatNet) {
|
|
3274
4262
|
const parts = [];
|
|
@@ -3279,10 +4267,10 @@ function formatInvariant(inv, flatNet) {
|
|
|
3279
4267
|
parts.push(flatNet.places[idx].name);
|
|
3280
4268
|
}
|
|
3281
4269
|
}
|
|
3282
|
-
return `${parts.join(" + ")} = ${inv.constant}`;
|
|
4270
|
+
return `${parts.length === 0 ? "0" : parts.join(" + ")} = ${inv.constant}`;
|
|
3283
4271
|
}
|
|
3284
|
-
function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics) {
|
|
3285
|
-
return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, elapsedMs, statistics };
|
|
4272
|
+
function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics, counterexampleConfirmed = null) {
|
|
4273
|
+
return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, counterexampleConfirmed, elapsedMs, statistics };
|
|
3286
4274
|
}
|
|
3287
4275
|
|
|
3288
4276
|
// src/verification/smt-verification-result.ts
|
|
@@ -3336,23 +4324,42 @@ export {
|
|
|
3336
4324
|
pInvariant,
|
|
3337
4325
|
pInvariantToString,
|
|
3338
4326
|
computePInvariants,
|
|
4327
|
+
strengthenWithSemiflows,
|
|
3339
4328
|
computePSemiflows,
|
|
3340
4329
|
isCoveredByInvariants,
|
|
4330
|
+
canonicalInvariantOrder,
|
|
3341
4331
|
structuralCheck,
|
|
3342
4332
|
findMinimalSiphons,
|
|
3343
4333
|
findMaximalTrapIn,
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
4334
|
+
Z3_ENV,
|
|
4335
|
+
DUMP_ENV,
|
|
4336
|
+
MIN_Z3_VERSION,
|
|
4337
|
+
parseZ3Version,
|
|
4338
|
+
formatZ3Version,
|
|
4339
|
+
Z3Unavailable,
|
|
4340
|
+
Z3ProcessError,
|
|
4341
|
+
z3SolverAt,
|
|
4342
|
+
resolveZ3,
|
|
4343
|
+
z3Available,
|
|
4344
|
+
runZ3Text,
|
|
4345
|
+
runZ3Spacer,
|
|
3348
4346
|
encode,
|
|
4347
|
+
encodeStepRelationSmt2,
|
|
4348
|
+
checkCertificate,
|
|
4349
|
+
vcScript,
|
|
3349
4350
|
DBM,
|
|
3350
4351
|
StateClass,
|
|
3351
4352
|
requireOutputProducingActions,
|
|
3352
4353
|
StateClassGraph,
|
|
3353
4354
|
decode,
|
|
4355
|
+
decodeStateSet,
|
|
4356
|
+
flatNetPlaceCount,
|
|
4357
|
+
flatNetTransitionCount,
|
|
4358
|
+
flatNetIndexOf,
|
|
4359
|
+
replayCounterexample,
|
|
3354
4360
|
SmtVerifier,
|
|
4361
|
+
placeholderCertificate,
|
|
3355
4362
|
isProven,
|
|
3356
4363
|
isViolated
|
|
3357
4364
|
};
|
|
3358
|
-
//# sourceMappingURL=chunk-
|
|
4365
|
+
//# sourceMappingURL=chunk-WYCGGQAW.js.map
|