libpetri 3.0.1 → 4.1.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 +5 -1
- package/dist/{chunk-JZIEWVAV.js → chunk-FD3S4TZM.js} +1330 -1077
- package/dist/chunk-FD3S4TZM.js.map +1 -0
- package/dist/{chunk-6NH64RCU.js → chunk-GJAHGHGT.js} +2 -2
- package/dist/debug/index.d.ts +2 -2
- package/dist/doclet/index.d.ts +1 -1
- package/dist/doclet/resources/petrinet-diagrams.js +6364 -6360
- package/dist/{event-store-BFX_yJ8I.d.ts → event-store-B3ppVTpO.d.ts} +1 -1
- package/dist/export/index.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/{petri-net-WSScMyDL.d.ts → petri-net-p0ONpSAO.d.ts} +32 -2
- package/dist/render-dom/index.js +1 -1
- package/dist/verification/index.d.ts +307 -214
- package/dist/verification/index.js +39 -7
- package/dist/verification/index.js.map +1 -1
- package/dist/viewer/index.js +1 -1
- package/dist/viewer/viewer.iife.js +6364 -6360
- package/package.json +4 -7
- package/dist/chunk-JZIEWVAV.js.map +0 -1
- /package/dist/{chunk-6NH64RCU.js.map → chunk-GJAHGHGT.js.map} +0 -0
|
@@ -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 {
|
|
@@ -666,41 +680,16 @@ function computePInvariants(matrix, flatNet, initialMarking) {
|
|
|
666
680
|
continue;
|
|
667
681
|
}
|
|
668
682
|
const weights = new Array(P);
|
|
669
|
-
let allNonNegative = true;
|
|
670
683
|
let hasPositive = false;
|
|
684
|
+
let hasNegative = false;
|
|
671
685
|
for (let i = 0; i < P; i++) {
|
|
672
686
|
weights[i] = augmented[row][T + i];
|
|
673
|
-
if (weights[i] < 0) {
|
|
674
|
-
allNonNegative = false;
|
|
675
|
-
break;
|
|
676
|
-
}
|
|
677
687
|
if (weights[i] > 0) hasPositive = true;
|
|
688
|
+
if (weights[i] < 0) hasNegative = true;
|
|
678
689
|
}
|
|
679
|
-
if (!
|
|
680
|
-
|
|
681
|
-
for (let i = 0; i < P; i++)
|
|
682
|
-
if (augmented[row][T + i] > 0) {
|
|
683
|
-
allNonPositive = false;
|
|
684
|
-
break;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
if (allNonPositive) {
|
|
688
|
-
for (let i = 0; i < P; i++) {
|
|
689
|
-
weights[i] = -augmented[row][T + i];
|
|
690
|
-
}
|
|
691
|
-
hasPositive = true;
|
|
692
|
-
allNonNegative = true;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
if (!allNonNegative || !hasPositive) continue;
|
|
696
|
-
let g = 0;
|
|
697
|
-
for (const w of weights) {
|
|
698
|
-
if (w > 0) g = gcd(g, w);
|
|
699
|
-
}
|
|
700
|
-
if (g > 1) {
|
|
701
|
-
for (let i = 0; i < P; i++) {
|
|
702
|
-
weights[i] = weights[i] / g;
|
|
703
|
-
}
|
|
690
|
+
if (!hasPositive && !hasNegative) continue;
|
|
691
|
+
if (!hasPositive) {
|
|
692
|
+
for (let i = 0; i < P; i++) weights[i] = -weights[i];
|
|
704
693
|
}
|
|
705
694
|
const support = /* @__PURE__ */ new Set();
|
|
706
695
|
let constant = 0;
|
|
@@ -814,6 +803,24 @@ function columnName(flatNet, t) {
|
|
|
814
803
|
const ft = flatNet.transitions[t];
|
|
815
804
|
return ft != null ? `transition '${ft.name}'` : `env-injector column ${t - flatNet.transitions.length}`;
|
|
816
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
|
+
}
|
|
817
824
|
function computePSemiflows(matrix, flatNet, initialMarking) {
|
|
818
825
|
const np = matrix.numPlaces();
|
|
819
826
|
const nt = matrix.numTransitions();
|
|
@@ -901,6 +908,7 @@ function keepSupportMinimal(rows) {
|
|
|
901
908
|
function isCoveredByInvariants(invariants, numPlaces) {
|
|
902
909
|
const covered = new Array(numPlaces).fill(false);
|
|
903
910
|
for (const inv of invariants) {
|
|
911
|
+
if (inv.weights.some((w) => w < 0)) continue;
|
|
904
912
|
for (const idx of inv.support) {
|
|
905
913
|
if (idx < numPlaces) covered[idx] = true;
|
|
906
914
|
}
|
|
@@ -928,6 +936,19 @@ function gcd(a, b) {
|
|
|
928
936
|
}
|
|
929
937
|
return a;
|
|
930
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
|
+
}
|
|
931
952
|
|
|
932
953
|
// src/verification/invariant/structural-check.ts
|
|
933
954
|
var MAX_PLACES_FOR_SIPHON_ANALYSIS = 50;
|
|
@@ -1090,544 +1111,675 @@ function isSubsetOf(sub, sup) {
|
|
|
1090
1111
|
return true;
|
|
1091
1112
|
}
|
|
1092
1113
|
|
|
1093
|
-
// src/verification/z3/
|
|
1094
|
-
import {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
async function query(errorExpr, reachableDecl) {
|
|
1104
|
-
try {
|
|
1105
|
-
const status = await fp.query(errorExpr);
|
|
1106
|
-
if (status === "unsat") {
|
|
1107
|
-
let invariantFormula = null;
|
|
1108
|
-
let provenAnswer = null;
|
|
1109
|
-
const levelInvariants = [];
|
|
1110
|
-
try {
|
|
1111
|
-
const answer = fp.getAnswer();
|
|
1112
|
-
if (answer != null) {
|
|
1113
|
-
invariantFormula = answer.toString();
|
|
1114
|
-
provenAnswer = answer;
|
|
1115
|
-
}
|
|
1116
|
-
} catch {
|
|
1117
|
-
}
|
|
1118
|
-
if (reachableDecl != null) {
|
|
1119
|
-
try {
|
|
1120
|
-
const levels = fp.getNumLevels(reachableDecl);
|
|
1121
|
-
for (let i = 0; i < levels; i++) {
|
|
1122
|
-
const cover = fp.getCoverDelta(i, reachableDecl);
|
|
1123
|
-
if (cover != null && !ctx.isTrue(cover)) {
|
|
1124
|
-
levelInvariants.push(`Level ${i}: ${cover.toString()}`);
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
} catch {
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
return { type: "proven", invariantFormula, levelInvariants, answer: provenAnswer };
|
|
1131
|
-
}
|
|
1132
|
-
if (status === "sat") {
|
|
1133
|
-
let answer = null;
|
|
1134
|
-
try {
|
|
1135
|
-
answer = fp.getAnswer();
|
|
1136
|
-
} catch {
|
|
1137
|
-
}
|
|
1138
|
-
return { type: "violated", answer };
|
|
1139
|
-
}
|
|
1140
|
-
return { type: "unknown", reason: fp.getReasonUnknown() };
|
|
1141
|
-
} catch (e) {
|
|
1142
|
-
return { type: "unknown", reason: `Z3 exception: ${e.message ?? e}` };
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
function dispose() {
|
|
1146
|
-
try {
|
|
1147
|
-
fp.release();
|
|
1148
|
-
} catch {
|
|
1149
|
-
}
|
|
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;
|
|
1150
1124
|
}
|
|
1151
|
-
return
|
|
1152
|
-
ctx,
|
|
1153
|
-
fp,
|
|
1154
|
-
query,
|
|
1155
|
-
dispose
|
|
1156
|
-
};
|
|
1125
|
+
return null;
|
|
1157
1126
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
function flatNetPlaceCount(net) {
|
|
1161
|
-
return net.places.length;
|
|
1127
|
+
function timeoutLine(stdout) {
|
|
1128
|
+
return stdout.split("\n").some((l) => l.trim() === "timeout");
|
|
1162
1129
|
}
|
|
1163
|
-
function
|
|
1164
|
-
|
|
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;
|
|
1165
1136
|
}
|
|
1166
|
-
function
|
|
1167
|
-
|
|
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");
|
|
1168
1176
|
}
|
|
1169
1177
|
|
|
1170
|
-
// src/verification/z3/
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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";
|
|
1186
1199
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
}
|
|
1193
|
-
for (const [name, bound] of flatNet.environmentInjection) {
|
|
1194
|
-
const idx = flatNet.placeIndex.get(name);
|
|
1195
|
-
if (idx == null) continue;
|
|
1196
|
-
encodeInjectionRule(ctx, fp, reachable, idx, bound, P);
|
|
1200
|
+
};
|
|
1201
|
+
var Z3ProcessError = class extends Error {
|
|
1202
|
+
constructor(message) {
|
|
1203
|
+
super(message);
|
|
1204
|
+
this.name = "Z3ProcessError";
|
|
1197
1205
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
reachableDecl: reachable
|
|
1202
|
-
};
|
|
1206
|
+
};
|
|
1207
|
+
function replySucceeded(reply) {
|
|
1208
|
+
return reply.exit.kind === "exited" && reply.exit.code === 0;
|
|
1203
1209
|
}
|
|
1204
|
-
function
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
const head = reachable.call(...mPrimeVars);
|
|
1220
|
-
const allVars = [...mVars, ...mPrimeVars];
|
|
1221
|
-
const rule = ctx.Implies(body, head);
|
|
1222
|
-
const qRule = ctx.ForAll(allVars, rule);
|
|
1223
|
-
fp.addRule(qRule, `t_${ft.name}`);
|
|
1224
|
-
}
|
|
1225
|
-
function encodeInjectionRule(ctx, fp, reachable, idx, bound, P) {
|
|
1226
|
-
const Int = ctx.Int;
|
|
1227
|
-
const mVars = [];
|
|
1228
|
-
const mPrimeVars = [];
|
|
1229
|
-
for (let i = 0; i < P; i++) {
|
|
1230
|
-
mVars.push(Int.const(`m${i}`));
|
|
1231
|
-
mPrimeVars.push(Int.const(`mp${i}`));
|
|
1232
|
-
}
|
|
1233
|
-
const reachBody = reachable.call(...mVars);
|
|
1234
|
-
const fire = encodeInjectionFire(ctx, idx, mVars, mPrimeVars, P);
|
|
1235
|
-
const guard = encodeInjectionGuard(ctx, idx, bound, mVars);
|
|
1236
|
-
const body = ctx.And(reachBody, guard, fire);
|
|
1237
|
-
const head = reachable.call(...mPrimeVars);
|
|
1238
|
-
const qRule = ctx.ForAll([...mVars, ...mPrimeVars], ctx.Implies(body, head));
|
|
1239
|
-
fp.addRule(qRule, `env_inject_${idx}`);
|
|
1240
|
-
}
|
|
1241
|
-
function encodeNonNegativity(ctx, vars, P) {
|
|
1242
|
-
let result = ctx.Bool.val(true);
|
|
1243
|
-
for (let i = 0; i < P; i++) {
|
|
1244
|
-
result = ctx.And(result, vars[i].ge(0));
|
|
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`;
|
|
1245
1225
|
}
|
|
1246
|
-
|
|
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()}`;
|
|
1247
1234
|
}
|
|
1248
|
-
function
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1235
|
+
function locateZ3(program, env = process.env) {
|
|
1236
|
+
const isFile = (p) => {
|
|
1237
|
+
try {
|
|
1238
|
+
return existsSync(p) && statSync(p).isFile();
|
|
1239
|
+
} catch {
|
|
1240
|
+
return false;
|
|
1254
1241
|
}
|
|
1242
|
+
};
|
|
1243
|
+
if (program.includes("/") || program.includes(path.sep) || path.isAbsolute(program)) {
|
|
1244
|
+
return isFile(program) ? program : null;
|
|
1255
1245
|
}
|
|
1256
|
-
|
|
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;
|
|
1257
1255
|
}
|
|
1258
|
-
function
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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`);
|
|
1265
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 };
|
|
1286
|
+
}
|
|
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 };
|
|
1293
|
+
}
|
|
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;
|
|
1266
1313
|
}
|
|
1267
|
-
return fire;
|
|
1268
1314
|
}
|
|
1269
|
-
function
|
|
1270
|
-
|
|
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
|
+
}
|
|
1271
1386
|
}
|
|
1272
|
-
|
|
1387
|
+
|
|
1388
|
+
// src/verification/z3/smt-encoder.ts
|
|
1389
|
+
function encode(flatNet, initialMarking, property, invariants, sinkPlaces = /* @__PURE__ */ new Set(), produceProofs = false) {
|
|
1273
1390
|
const P = flatNet.places.length;
|
|
1274
|
-
const
|
|
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("");
|
|
1275
1405
|
for (const ft of flatNet.transitions) {
|
|
1276
|
-
|
|
1277
|
-
const fire = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
|
|
1278
|
-
const nonNeg = encodeNonNegativity(ctx, mPrimeVars, P);
|
|
1279
|
-
const envBounds = encodeEnvBounds(ctx, flatNet, mPrimeVars);
|
|
1280
|
-
disjuncts.push(ctx.And(enabled, fire, nonNeg, envBounds));
|
|
1406
|
+
lines.push(encodeTransitionRule(flatNet, ft, mVars, mpVars, invariants));
|
|
1281
1407
|
}
|
|
1282
|
-
for (const
|
|
1283
|
-
|
|
1284
|
-
if (idx == null) continue;
|
|
1285
|
-
disjuncts.push(ctx.And(
|
|
1286
|
-
encodeInjectionGuard(ctx, idx, bound, mVars),
|
|
1287
|
-
encodeInjectionFire(ctx, idx, mVars, mPrimeVars, P)
|
|
1288
|
-
));
|
|
1408
|
+
for (const inj of envInject) {
|
|
1409
|
+
lines.push(encodeInjectionRule(P, inj.pid, inj.bound, mVars, mpVars));
|
|
1289
1410
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
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 };
|
|
1292
1419
|
}
|
|
1293
|
-
function
|
|
1294
|
-
const out =
|
|
1420
|
+
function resolveEnvInjection(flatNet) {
|
|
1421
|
+
const out = [];
|
|
1295
1422
|
for (const [name, bound] of flatNet.environmentInjection) {
|
|
1296
|
-
const
|
|
1297
|
-
if (
|
|
1423
|
+
const pid = flatNet.placeIndex.get(name);
|
|
1424
|
+
if (pid != null) out.push({ pid, bound });
|
|
1298
1425
|
}
|
|
1426
|
+
out.sort((a, b) => a.pid - b.pid);
|
|
1299
1427
|
return out;
|
|
1300
1428
|
}
|
|
1301
|
-
function
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
if (pre <= 0) continue;
|
|
1307
|
-
if (envInj?.has(p)) {
|
|
1308
|
-
const bound = envInj.get(p);
|
|
1309
|
-
if (bound !== null && pre > bound) return ctx.Bool.val(false);
|
|
1310
|
-
continue;
|
|
1311
|
-
}
|
|
1312
|
-
result = ctx.And(result, mVars[p].ge(pre));
|
|
1313
|
-
}
|
|
1314
|
-
for (const p of ft.readPlaces) {
|
|
1315
|
-
if (envInj?.has(p)) {
|
|
1316
|
-
const bound = envInj.get(p);
|
|
1317
|
-
if (bound !== null && bound < 1) return ctx.Bool.val(false);
|
|
1318
|
-
continue;
|
|
1319
|
-
}
|
|
1320
|
-
result = ctx.And(result, mVars[p].ge(1));
|
|
1321
|
-
}
|
|
1322
|
-
for (const p of ft.inhibitorPlaces) {
|
|
1323
|
-
result = ctx.And(result, mVars[p].eq(0));
|
|
1324
|
-
}
|
|
1325
|
-
for (let p = 0; p < P; p++) {
|
|
1326
|
-
result = ctx.And(result, mVars[p].ge(0));
|
|
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]);
|
|
1327
1434
|
}
|
|
1328
|
-
|
|
1435
|
+
out.sort((a, b) => a[0] - b[0]);
|
|
1436
|
+
return out;
|
|
1329
1437
|
}
|
|
1330
|
-
function
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
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 = [];
|
|
1452
|
+
for (let i = 0; i < P; i++) {
|
|
1453
|
+
if (ft.preVector[i] > 0) conditions.push(`(>= ${mVars[i]} ${ft.preVector[i]})`);
|
|
1454
|
+
}
|
|
1455
|
+
for (const inh of ft.inhibitorPlaces) conditions.push(`(= ${mVars[inh]} 0)`);
|
|
1456
|
+
for (const rd of ft.readPlaces) conditions.push(`(>= ${mVars[rd]} 1)`);
|
|
1457
|
+
for (let i = 0; i < P; i++) {
|
|
1458
|
+
if (ft.resetPlaces.includes(i) || ft.consumeAll[i]) {
|
|
1459
|
+
conditions.push(`(= ${mpVars[i]} ${ft.postVector[i]})`);
|
|
1336
1460
|
} else {
|
|
1337
|
-
const delta = ft.postVector[
|
|
1338
|
-
if (delta
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
result = ctx.And(result, mPrimeVars[p].eq(mVars[p].add(delta)));
|
|
1342
|
-
}
|
|
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]})`);
|
|
1343
1465
|
}
|
|
1344
1466
|
}
|
|
1345
|
-
|
|
1467
|
+
for (let i = 0; i < P; i++) conditions.push(`(>= ${mpVars[i]} 0)`);
|
|
1468
|
+
return conditions;
|
|
1346
1469
|
}
|
|
1347
|
-
function
|
|
1348
|
-
const
|
|
1349
|
-
const
|
|
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})`);
|
|
1477
|
+
}
|
|
1478
|
+
return conditions;
|
|
1479
|
+
}
|
|
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})`);
|
|
1350
1486
|
for (let i = 0; i < P; i++) {
|
|
1351
|
-
|
|
1487
|
+
if (i === pid) conditions.push(`(= ${mpVars[i]} (+ ${mVars[i]} 1))`);
|
|
1488
|
+
else conditions.push(`(= ${mpVars[i]} ${mVars[i]})`);
|
|
1489
|
+
}
|
|
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));
|
|
1524
|
+
}
|
|
1525
|
+
for (const inj of resolveEnvInjection(flatNet)) {
|
|
1526
|
+
disjuncts.push(conjoin(injectionConditions(P, inj.pid, inj.bound, mVars, mpVars)));
|
|
1527
|
+
}
|
|
1528
|
+
if (disjuncts.length === 0) return "false";
|
|
1529
|
+
if (disjuncts.length === 1) return disjuncts[0];
|
|
1530
|
+
return `(or ${disjuncts.join("\n ")})`;
|
|
1531
|
+
}
|
|
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)))`;
|
|
1537
|
+
}
|
|
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);
|
|
1352
1543
|
}
|
|
1353
|
-
|
|
1354
|
-
const violation = encodePropertyViolation(ctx, flatNet, property, sinkPlaces, mVars, P);
|
|
1355
|
-
const head = error.call();
|
|
1356
|
-
const body = ctx.And(reachBody, violation);
|
|
1357
|
-
const rule = ctx.Implies(body, head);
|
|
1358
|
-
const qRule = ctx.ForAll(mVars, rule);
|
|
1359
|
-
fp.addRule(qRule, `error_${property.type}`);
|
|
1544
|
+
return [...idx].sort((a, b) => a - b);
|
|
1360
1545
|
}
|
|
1361
|
-
function encodePropertyViolation(
|
|
1546
|
+
function encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject) {
|
|
1362
1547
|
switch (property.type) {
|
|
1363
|
-
case "deadlock-free":
|
|
1364
|
-
|
|
1365
|
-
if (sinkPlaces.size > 0) {
|
|
1366
|
-
let notAtSink = ctx.Bool.val(true);
|
|
1367
|
-
for (const sink of sinkPlaces) {
|
|
1368
|
-
const idx = flatNetIndexOf(flatNet, sink);
|
|
1369
|
-
if (idx >= 0) {
|
|
1370
|
-
notAtSink = ctx.And(notAtSink, mVars[idx].eq(0));
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
return ctx.And(deadlock, notAtSink);
|
|
1374
|
-
}
|
|
1375
|
-
return deadlock;
|
|
1376
|
-
}
|
|
1548
|
+
case "deadlock-free":
|
|
1549
|
+
return encodeDeadlock(flatNet, mVars, sinkPlaces, envInject);
|
|
1377
1550
|
case "mutual-exclusion": {
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
if (idx1 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p1.name}`);
|
|
1381
|
-
if (idx2 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p2.name}`);
|
|
1382
|
-
return ctx.And(mVars[idx1].ge(1), mVars[idx2].ge(1));
|
|
1383
|
-
}
|
|
1384
|
-
case "place-bound": {
|
|
1385
|
-
const idx = flatNetIndexOf(flatNet, property.place);
|
|
1386
|
-
if (idx < 0) throw new Error(`PlaceBound references unknown place: ${property.place.name}`);
|
|
1387
|
-
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(" ")})`;
|
|
1388
1553
|
}
|
|
1554
|
+
case "place-bound":
|
|
1389
1555
|
case "branch-place-bound": {
|
|
1390
|
-
const
|
|
1391
|
-
|
|
1392
|
-
return mVars[idx].gt(property.bound);
|
|
1393
|
-
}
|
|
1394
|
-
case "joined-or-dead-lettered": {
|
|
1395
|
-
const idx = flatNetIndexOf(flatNet, property.pending);
|
|
1396
|
-
if (idx < 0) return ctx.Bool.val(false);
|
|
1397
|
-
const deadlock = encodeDeadlock(ctx, flatNet, mVars, P);
|
|
1398
|
-
return ctx.And(deadlock, mVars[idx].ge(1));
|
|
1556
|
+
const pid = flatNet.placeIndex.get(property.place.name);
|
|
1557
|
+
return pid == null ? "false" : `(> ${mVars[pid]} ${property.bound})`;
|
|
1399
1558
|
}
|
|
1400
1559
|
case "unreachable": {
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1408
|
-
return allMarked;
|
|
1560
|
+
const conditions = indexOrdered(flatNet, property.places).map((i) => `(>= ${mVars[i]} 1)`);
|
|
1561
|
+
return conditions.length === 0 ? "false" : `(and ${conditions.join(" ")})`;
|
|
1562
|
+
}
|
|
1563
|
+
case "joined-or-dead-lettered": {
|
|
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))`;
|
|
1409
1567
|
}
|
|
1410
1568
|
}
|
|
1411
1569
|
}
|
|
1412
|
-
function encodeDeadlock(
|
|
1413
|
-
|
|
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 = [];
|
|
1414
1574
|
for (const ft of flatNet.transitions) {
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
if (idx < P) {
|
|
1434
|
-
sum = sum.add(mVars[idx].mul(inv.weights[idx]));
|
|
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;
|
|
1583
|
+
}
|
|
1584
|
+
disableReasons.push(`(< ${mVars[i]} ${ft.preVector[i]})`);
|
|
1585
|
+
}
|
|
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;
|
|
1435
1593
|
}
|
|
1594
|
+
disableReasons.push(`(< ${mVars[rd]} 1)`);
|
|
1595
|
+
}
|
|
1596
|
+
if (permanentlyDisabled) {
|
|
1597
|
+
disabledConditions.push("true");
|
|
1598
|
+
continue;
|
|
1436
1599
|
}
|
|
1437
|
-
|
|
1600
|
+
if (disableReasons.length === 0) return "false";
|
|
1601
|
+
disabledConditions.push(`(or ${disableReasons.join(" ")})`);
|
|
1438
1602
|
}
|
|
1439
|
-
|
|
1603
|
+
for (const pid of indexOrdered(flatNet, sinkPlaces)) {
|
|
1604
|
+
disabledConditions.push(`(= ${mVars[pid]} 0)`);
|
|
1605
|
+
}
|
|
1606
|
+
return disabledConditions.length === 0 ? "true" : `(and ${disabledConditions.join("\n ")})`;
|
|
1607
|
+
}
|
|
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;
|
|
1440
1612
|
}
|
|
1441
1613
|
|
|
1442
1614
|
// src/verification/z3/certificate-checker.ts
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
return {
|
|
1447
|
-
type: "unavailable",
|
|
1448
|
-
reason: "invariant missing (Z3 produced no inductive-invariant answer)",
|
|
1449
|
-
invariant: null
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
const P = flatNet.places.length;
|
|
1453
|
-
const Int = ctx.Int;
|
|
1454
|
-
const mVars = [];
|
|
1455
|
-
const mPrimeVars = [];
|
|
1456
|
-
for (let i = 0; i < P; i++) {
|
|
1457
|
-
mVars.push(Int.const(`m${i}`));
|
|
1458
|
-
mPrimeVars.push(Int.const(`mp${i}`));
|
|
1459
|
-
}
|
|
1460
|
-
const invariant = extractInvariant(ctx, answer, P, mVars);
|
|
1461
|
-
if (invariant == null) {
|
|
1462
|
-
return {
|
|
1463
|
-
type: "unavailable",
|
|
1464
|
-
reason: "invariant unparseable (no Reachable definition recognized in the Z3 answer)",
|
|
1465
|
-
invariant: String(answer)
|
|
1466
|
-
};
|
|
1467
|
-
}
|
|
1468
|
-
const candidate = invariants.length > 0 ? ctx.And(invariant, encodeInvariantConstraints(ctx, invariants, mVars, P)) : invariant;
|
|
1469
|
-
const failure = await validateCandidate(
|
|
1470
|
-
ctx,
|
|
1471
|
-
candidate,
|
|
1472
|
-
flatNet,
|
|
1473
|
-
initialMarking,
|
|
1474
|
-
property,
|
|
1475
|
-
sinkPlaces,
|
|
1476
|
-
timeoutMs,
|
|
1477
|
-
mVars,
|
|
1478
|
-
mPrimeVars
|
|
1479
|
-
);
|
|
1480
|
-
if (failure == null) {
|
|
1481
|
-
return { type: "passed", invariant: String(candidate) };
|
|
1482
|
-
}
|
|
1483
|
-
return { type: "failed", vc: failure.vc, detail: failure.detail, invariant: String(candidate) };
|
|
1484
|
-
} catch (e) {
|
|
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) {
|
|
1485
1618
|
return {
|
|
1486
1619
|
type: "unavailable",
|
|
1487
|
-
reason:
|
|
1620
|
+
reason: "no inductive invariant (define-fun block) could be extracted from the z3 model",
|
|
1488
1621
|
invariant: null
|
|
1489
1622
|
};
|
|
1490
1623
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
let nonNegM = ctx.Bool.val(true);
|
|
1496
|
-
for (let i = 0; i < P; i++) {
|
|
1497
|
-
nonNegM = ctx.And(nonNegM, mVars[i].ge(0));
|
|
1498
|
-
}
|
|
1499
|
-
const initPairs = mVars.map((v, i) => [
|
|
1500
|
-
v,
|
|
1501
|
-
Int.val(initialMarking.tokens(flatNet.places[i]))
|
|
1502
|
-
]);
|
|
1503
|
-
const candidateAtInit = ctx.substitute(candidate, ...initPairs);
|
|
1504
|
-
const vc1 = await checkUnsat(ctx, flatNet, mVars, timeoutMs, "initiation (VC1)", [ctx.Not(candidateAtInit)]);
|
|
1505
|
-
if (vc1 != null) return vc1;
|
|
1506
|
-
const primePairs = mVars.map((v, i) => [v, mPrimeVars[i]]);
|
|
1507
|
-
const candidatePrime = ctx.substitute(candidate, ...primePairs);
|
|
1508
|
-
const step = encodeStepRelation(ctx, flatNet, mVars, mPrimeVars);
|
|
1509
|
-
const vc2 = await checkUnsat(
|
|
1510
|
-
ctx,
|
|
1511
|
-
flatNet,
|
|
1512
|
-
mVars,
|
|
1513
|
-
timeoutMs,
|
|
1514
|
-
"consecution (VC2)",
|
|
1515
|
-
[candidate, nonNegM, step, ctx.Not(candidatePrime)]
|
|
1516
|
-
);
|
|
1517
|
-
if (vc2 != null) return vc2;
|
|
1518
|
-
const bad = encodePropertyViolation(ctx, flatNet, property, sinkPlaces, mVars, P);
|
|
1519
|
-
const vc3 = await checkUnsat(ctx, flatNet, mVars, timeoutMs, "safety (VC3)", [candidate, nonNegM, bad]);
|
|
1520
|
-
if (vc3 != null) return vc3;
|
|
1521
|
-
return null;
|
|
1522
|
-
}
|
|
1523
|
-
function extractInvariant(ctx, answer, P, mVars) {
|
|
1524
|
-
for (const conjunct of topLevelConjuncts(ctx, answer)) {
|
|
1525
|
-
const invariant = tryExtractDefinition(ctx, conjunct, P, mVars);
|
|
1526
|
-
if (invariant != null) return invariant;
|
|
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 };
|
|
1527
1628
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
for (let i = 0; i < n; i++) out.push(expr.arg(i));
|
|
1535
|
-
return out;
|
|
1536
|
-
}
|
|
1537
|
-
return [expr];
|
|
1538
|
-
}
|
|
1539
|
-
function tryExtractDefinition(ctx, expr, P, mVars) {
|
|
1540
|
-
if (ctx.isQuantifier(expr) && expr.is_forall()) {
|
|
1541
|
-
const q = expr;
|
|
1542
|
-
const parts2 = splitDefinition(ctx, q.body(), P);
|
|
1543
|
-
if (parts2 == null) return null;
|
|
1544
|
-
const numVars = q.num_vars();
|
|
1545
|
-
const to = new Array(numVars);
|
|
1546
|
-
for (let j = 0; j < P; j++) {
|
|
1547
|
-
const arg = parts2.app.arg(j);
|
|
1548
|
-
if (!ctx.isVar(arg)) return null;
|
|
1549
|
-
const idx = ctx.getVarIndex(arg);
|
|
1550
|
-
if (idx >= numVars || to[idx] != null) return null;
|
|
1551
|
-
to[idx] = mVars[j];
|
|
1552
|
-
}
|
|
1553
|
-
for (let i = 0; i < numVars; i++) {
|
|
1554
|
-
if (to[i] == null) to[i] = ctx.Int.const(`certFree${i}`);
|
|
1555
|
-
}
|
|
1556
|
-
return ctx.substituteVars(parts2.phi, ...to);
|
|
1557
|
-
}
|
|
1558
|
-
const parts = splitDefinition(ctx, expr, P);
|
|
1559
|
-
if (parts == null) return null;
|
|
1560
|
-
const pairs = [];
|
|
1561
|
-
for (let j = 0; j < P; j++) {
|
|
1562
|
-
const arg = parts.app.arg(j);
|
|
1563
|
-
if (ctx.isVar(arg)) return null;
|
|
1564
|
-
pairs.push([arg, mVars[j]]);
|
|
1565
|
-
}
|
|
1566
|
-
if (pairs.length === 0) return parts.phi;
|
|
1567
|
-
return ctx.substitute(parts.phi, ...pairs);
|
|
1568
|
-
}
|
|
1569
|
-
function splitDefinition(ctx, body, P) {
|
|
1570
|
-
if (ctx.isQuantifier(body) || !ctx.isApp(body)) return null;
|
|
1571
|
-
const b = body;
|
|
1572
|
-
const decl = String(b.decl().name());
|
|
1573
|
-
if ((decl === "=" || decl === "iff") && b.numArgs() === 2) {
|
|
1574
|
-
if (isReachableApp(ctx, b.arg(0), P)) return { app: b.arg(0), phi: b.arg(1) };
|
|
1575
|
-
if (isReachableApp(ctx, b.arg(1), P)) return { app: b.arg(1), phi: b.arg(0) };
|
|
1576
|
-
return null;
|
|
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 };
|
|
1577
1635
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
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
|
+
}
|
|
1580
1641
|
}
|
|
1581
|
-
return
|
|
1642
|
+
return { type: "passed", invariant: certificate };
|
|
1582
1643
|
}
|
|
1583
|
-
function
|
|
1584
|
-
return
|
|
1644
|
+
function vcScript(certificate, flatNet, initialMarking, property, sinkPlaces, invariants) {
|
|
1645
|
+
return script(buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants));
|
|
1585
1646
|
}
|
|
1586
|
-
|
|
1587
|
-
const
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
if (status === "unsat") return null;
|
|
1596
|
-
if (status === "sat") {
|
|
1597
|
-
const witness = describeWitness(solver, flatNet, mVars);
|
|
1598
|
-
return {
|
|
1599
|
-
vc,
|
|
1600
|
-
detail: `solver returned SATISFIABLE${witness == null ? "" : ` (witness: ${witness})`}`
|
|
1601
|
-
};
|
|
1647
|
+
function shapeFailure(flatNet, invariants) {
|
|
1648
|
+
const P = flatNet.places.length;
|
|
1649
|
+
for (const inv of invariants) {
|
|
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`;
|
|
1655
|
+
}
|
|
1602
1656
|
}
|
|
1603
|
-
|
|
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 = "";
|
|
1604
1736
|
try {
|
|
1605
|
-
|
|
1737
|
+
reply = (await runZ3Text(solver, lines.join("\n"), "certificate-detail", timeoutMs, [])).stdout;
|
|
1606
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})`;
|
|
1607
1744
|
}
|
|
1608
|
-
|
|
1745
|
+
const r = reasonUnknown(reply);
|
|
1746
|
+
return r == null ? "solver returned UNKNOWN" : `solver returned UNKNOWN (${r})`;
|
|
1609
1747
|
}
|
|
1610
|
-
function
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
const
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
const
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
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);
|
|
1626
1765
|
}
|
|
1627
|
-
|
|
1628
|
-
} catch {
|
|
1629
|
-
return null;
|
|
1766
|
+
parts.push(`${flatNet.places[i].name}=${value}`);
|
|
1630
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)]);
|
|
1631
1783
|
}
|
|
1632
1784
|
|
|
1633
1785
|
// src/verification/analysis/dbm.ts
|
|
@@ -2163,90 +2315,89 @@ function consumeFromPlace(builder, place, count, environmentPlaces, environmentM
|
|
|
2163
2315
|
}
|
|
2164
2316
|
|
|
2165
2317
|
// src/verification/z3/counterexample-decoder.ts
|
|
2166
|
-
function
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
}
|
|
2170
|
-
switch (failure.kind) {
|
|
2171
|
-
case "no-answer":
|
|
2172
|
-
return "Z3 produced no derivation answer";
|
|
2173
|
-
case "traversal-error":
|
|
2174
|
-
return `derivation walk failed: ${failure.message}`;
|
|
2175
|
-
case "non-concrete":
|
|
2176
|
-
return `${failure.skipped} Reachable application(s) had non-concrete arguments`;
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
function decode(ctx, answer, flatNet) {
|
|
2180
|
-
const trace = [];
|
|
2181
|
-
const transitions = [];
|
|
2182
|
-
const stateByKey = /* @__PURE__ */ new Map();
|
|
2183
|
-
if (answer == null) {
|
|
2184
|
-
return { trace, transitions, states: /* @__PURE__ */ new Set(), failure: { kind: "no-answer" } };
|
|
2185
|
-
}
|
|
2186
|
-
const counters = { skipped: 0 };
|
|
2187
|
-
let failure = null;
|
|
2188
|
-
try {
|
|
2189
|
-
extractTrace(ctx, answer, flatNet, trace, transitions, stateByKey, counters);
|
|
2190
|
-
} catch (e) {
|
|
2191
|
-
failure = { kind: "traversal-error", message: String(e?.message ?? e) };
|
|
2192
|
-
}
|
|
2193
|
-
if (failure == null && counters.skipped > 0) {
|
|
2194
|
-
failure = { kind: "non-concrete", skipped: counters.skipped };
|
|
2195
|
-
}
|
|
2196
|
-
return { trace, transitions, states: new Set(stateByKey.values()), failure };
|
|
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 };
|
|
2197
2321
|
}
|
|
2198
|
-
function
|
|
2199
|
-
|
|
2200
|
-
if (!ctx.isApp(expr)) return;
|
|
2201
|
-
let name;
|
|
2202
|
-
try {
|
|
2203
|
-
const decl = expr.decl();
|
|
2204
|
-
name = String(decl.name());
|
|
2205
|
-
} catch {
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2322
|
+
function decodeStateSet(answer, flatNet) {
|
|
2323
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2208
2324
|
const P = flatNet.places.length;
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
const
|
|
2213
|
-
if (
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
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);
|
|
2219
2343
|
}
|
|
2220
2344
|
}
|
|
2221
2345
|
}
|
|
2222
|
-
|
|
2223
|
-
const numArgs = expr.numArgs();
|
|
2224
|
-
for (let i = 0; i < numArgs; i++) {
|
|
2225
|
-
const child = expr.arg(i);
|
|
2226
|
-
extractTrace(ctx, child, flatNet, trace, transitions, stateByKey, counters);
|
|
2227
|
-
}
|
|
2228
|
-
} catch {
|
|
2229
|
-
}
|
|
2230
|
-
if (name.startsWith("t_")) {
|
|
2231
|
-
transitions.push(name.substring(2));
|
|
2232
|
-
}
|
|
2346
|
+
return new Set(byKey.values());
|
|
2233
2347
|
}
|
|
2234
|
-
function
|
|
2235
|
-
const P = flatNet.places.length;
|
|
2236
|
-
if (reachableApp.numArgs() !== P) return null;
|
|
2348
|
+
function toMarking(args, flatNet) {
|
|
2237
2349
|
const builder = MarkingState.builder();
|
|
2238
|
-
for (let i = 0; i <
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
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();
|
|
2245
2371
|
} else {
|
|
2246
|
-
|
|
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();
|
|
2247
2384
|
}
|
|
2248
2385
|
}
|
|
2249
|
-
return
|
|
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;
|
|
2250
2401
|
}
|
|
2251
2402
|
|
|
2252
2403
|
// src/verification/z3/abstract-replayer.ts
|
|
@@ -2487,25 +2638,29 @@ function colourSlotBound(coloured, semiflows) {
|
|
|
2487
2638
|
const isSemiflow = (inv) => inv.weights.every((x) => x >= 0);
|
|
2488
2639
|
let single = null;
|
|
2489
2640
|
for (const inv of semiflows) {
|
|
2490
|
-
if (isSemiflow(inv) &&
|
|
2641
|
+
if (isSemiflow(inv) && coloured.every((pid) => w(inv, pid) >= 1)) {
|
|
2491
2642
|
if (single === null || inv.constant < single) single = inv.constant;
|
|
2492
2643
|
}
|
|
2493
2644
|
}
|
|
2494
2645
|
if (single !== null) return single;
|
|
2495
|
-
let sumConst = 0;
|
|
2496
2646
|
const covered = new Array(coloured.length).fill(false);
|
|
2497
2647
|
for (const inv of semiflows) {
|
|
2498
|
-
if (!isSemiflow(inv)) continue;
|
|
2499
|
-
let touches = false;
|
|
2648
|
+
if (!isSemiflow(inv) || inv.constant !== 0) continue;
|
|
2500
2649
|
for (let i = 0; i < coloured.length; i++) {
|
|
2501
|
-
if (w(inv, coloured[i]) >= 1)
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
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;
|
|
2505
2660
|
}
|
|
2506
|
-
|
|
2661
|
+
sumConst += inv.constant;
|
|
2507
2662
|
}
|
|
2508
|
-
if (covered.every((c) => c)
|
|
2663
|
+
if (covered.every((c) => c)) return sumConst;
|
|
2509
2664
|
return null;
|
|
2510
2665
|
}
|
|
2511
2666
|
function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrierPlaces, semiflows) {
|
|
@@ -2535,6 +2690,7 @@ function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrie
|
|
|
2535
2690
|
}
|
|
2536
2691
|
const k = colourSlotBound(coloured, semiflows);
|
|
2537
2692
|
if (k === null) return null;
|
|
2693
|
+
if (k === 0 && coloured.length === P) return null;
|
|
2538
2694
|
const budgetIdx = /* @__PURE__ */ new Set();
|
|
2539
2695
|
for (const n of budgetNames) {
|
|
2540
2696
|
const i = flat.placeIndex.get(n);
|
|
@@ -2570,324 +2726,278 @@ function buildColouredPlan(net, flat, initial, budgetNames, fragmentMode, carrie
|
|
|
2570
2726
|
}
|
|
2571
2727
|
return { coloured, isColoured, k, classes };
|
|
2572
2728
|
}
|
|
2573
|
-
function buildLayout(
|
|
2729
|
+
function buildLayout(plan, P) {
|
|
2574
2730
|
const colUnc = new Array(P).fill(-1);
|
|
2575
2731
|
const colCol = Array.from({ length: P }, () => []);
|
|
2576
|
-
|
|
2732
|
+
const cur = [];
|
|
2733
|
+
const nxt = [];
|
|
2577
2734
|
for (let i = 0; i < P; i++) {
|
|
2578
2735
|
if (plan.isColoured[i]) {
|
|
2579
2736
|
const idxs = [];
|
|
2580
|
-
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
|
+
}
|
|
2581
2742
|
colCol[i] = idxs;
|
|
2582
2743
|
} else {
|
|
2583
|
-
colUnc[i] =
|
|
2744
|
+
colUnc[i] = cur.length;
|
|
2745
|
+
cur.push(`m${i}`);
|
|
2746
|
+
nxt.push(`m${i}p`);
|
|
2584
2747
|
}
|
|
2585
2748
|
}
|
|
2586
|
-
|
|
2587
|
-
const nxt = [];
|
|
2588
|
-
for (let col = 0; col < nCols; col++) {
|
|
2589
|
-
cur.push(ctx.Int.const(`c${col}`));
|
|
2590
|
-
nxt.push(ctx.Int.const(`cp${col}`));
|
|
2591
|
-
}
|
|
2592
|
-
return { colUnc, colCol, nCols, cur, nxt };
|
|
2749
|
+
return { colUnc, colCol, cur, nxt };
|
|
2593
2750
|
}
|
|
2594
|
-
function
|
|
2751
|
+
function quantified2(names) {
|
|
2752
|
+
return names.map((v) => `(${v} Int)`).join(" ");
|
|
2753
|
+
}
|
|
2754
|
+
function encodeColoured(plan, flat, initial, property, invariants, sinkPlaces) {
|
|
2595
2755
|
const P = flat.places.length;
|
|
2596
2756
|
const k = plan.k;
|
|
2597
|
-
const lay = buildLayout(
|
|
2598
|
-
const
|
|
2599
|
-
const
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
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 = [];
|
|
2606
2766
|
for (let i = 0; i < P; i++) {
|
|
2607
2767
|
if (plan.isColoured[i]) {
|
|
2608
|
-
for (let c = 0; c < k; c++)
|
|
2768
|
+
for (let c = 0; c < k; c++) init.push("0");
|
|
2609
2769
|
} else {
|
|
2610
|
-
|
|
2770
|
+
init.push(String(initial.tokens(flat.places[i])));
|
|
2611
2771
|
}
|
|
2612
2772
|
}
|
|
2613
|
-
|
|
2773
|
+
lines.push(`(assert (Reachable ${init.join(" ")}))`);
|
|
2774
|
+
lines.push("");
|
|
2614
2775
|
for (let ti = 0; ti < plan.classes.length; ti++) {
|
|
2615
2776
|
const cls = plan.classes[ti];
|
|
2616
2777
|
const ft = flat.transitions[ti];
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_consume_${cc}`, (enab, upd) => {
|
|
2660
|
-
uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
|
|
2661
|
-
const icol = lay.colCol[inputCol][cc];
|
|
2662
|
-
enab.push(lay.cur[icol].ge(1));
|
|
2663
|
-
upd.set(icol, lay.cur[icol].add(-1));
|
|
2664
|
-
for (const o of colouredOut) {
|
|
2665
|
-
const ocol = lay.colCol[o][cc];
|
|
2666
|
-
upd.set(ocol, lay.cur[ocol].add(1));
|
|
2667
|
-
}
|
|
2668
|
-
});
|
|
2669
|
-
}
|
|
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;
|
|
2670
2820
|
}
|
|
2671
2821
|
}
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
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 };
|
|
2679
2830
|
}
|
|
2680
|
-
function
|
|
2831
|
+
function encodeRule(plan, lay, invariants, fill) {
|
|
2681
2832
|
const enab = [];
|
|
2682
|
-
const upd =
|
|
2833
|
+
const upd = [];
|
|
2683
2834
|
fill(enab, upd);
|
|
2684
|
-
const
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
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)`);
|
|
2689
2843
|
} else {
|
|
2690
|
-
|
|
2844
|
+
conditions.push(`(= ${lay.nxt[col]} ${lay.cur[col]})`);
|
|
2691
2845
|
}
|
|
2692
2846
|
}
|
|
2693
2847
|
for (const inv of invariants) {
|
|
2694
|
-
const eq = liftedInvariant(
|
|
2695
|
-
if (eq)
|
|
2848
|
+
const eq = liftedInvariant(inv, plan, lay, lay.nxt);
|
|
2849
|
+
if (eq != null) conditions.push(eq);
|
|
2696
2850
|
}
|
|
2697
|
-
const body =
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2851
|
+
const body = `(and ${conditions.join("\n ")})`;
|
|
2852
|
+
return `(assert (forall (${quantified2([...lay.cur, ...lay.nxt])})
|
|
2853
|
+
(=> ${body}
|
|
2854
|
+
(Reachable ${lay.nxt.join(" ")}))))`;
|
|
2701
2855
|
}
|
|
2702
|
-
function uncolouredIncidence(
|
|
2856
|
+
function uncolouredIncidence(lay, plan, ft, enab, upd) {
|
|
2703
2857
|
const P = ft.preVector.length;
|
|
2704
2858
|
for (let i = 0; i < P; i++) {
|
|
2705
2859
|
if (plan.isColoured[i]) continue;
|
|
2706
2860
|
const col = lay.colUnc[i];
|
|
2707
2861
|
const pre = ft.preVector[i];
|
|
2708
|
-
if (pre > 0) enab.push(lay.cur[col]
|
|
2862
|
+
if (pre > 0) enab.push(`(>= ${lay.cur[col]} ${pre})`);
|
|
2709
2863
|
if (ft.resetPlaces.includes(i) || ft.consumeAll[i]) {
|
|
2710
|
-
upd.
|
|
2864
|
+
upd.push({ col, expr: String(ft.postVector[i]) });
|
|
2711
2865
|
} else {
|
|
2712
2866
|
const delta = ft.postVector[i] - ft.preVector[i];
|
|
2713
|
-
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})` });
|
|
2714
2869
|
}
|
|
2715
2870
|
}
|
|
2716
|
-
for (const pid of ft.inhibitorPlaces) enab.push(lay.cur[lay.colUnc[pid]]
|
|
2717
|
-
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)`);
|
|
2718
2873
|
}
|
|
2719
|
-
function aggregate(plan, lay, place,
|
|
2874
|
+
function aggregate(plan, lay, place, names) {
|
|
2720
2875
|
if (plan.isColoured[place]) {
|
|
2721
2876
|
const cols = lay.colCol[place];
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
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(" ")})`;
|
|
2725
2880
|
}
|
|
2726
|
-
return
|
|
2881
|
+
return names[lay.colUnc[place]];
|
|
2727
2882
|
}
|
|
2728
|
-
function liftedInvariant(
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
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);
|
|
2733
2887
|
const w = inv.weights[i];
|
|
2734
|
-
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
const
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
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})`;
|
|
2893
|
+
}
|
|
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
|
+
};
|
|
2749
2906
|
switch (property.type) {
|
|
2750
2907
|
case "place-bound":
|
|
2751
2908
|
case "branch-place-bound": {
|
|
2752
|
-
const
|
|
2753
|
-
if (
|
|
2754
|
-
return aggregate(plan, lay,
|
|
2755
|
-
}
|
|
2756
|
-
case "mutual-exclusion": {
|
|
2757
|
-
const i1 = flatNetIndexOf(flat, property.p1);
|
|
2758
|
-
const i2 = flatNetIndexOf(flat, property.p2);
|
|
2759
|
-
if (i1 < 0 || i2 < 0) return ctx.Bool.val(false);
|
|
2760
|
-
return ctx.And(aggregate(plan, lay, i1, cur).ge(1), aggregate(plan, lay, i2, cur).ge(1));
|
|
2761
|
-
}
|
|
2762
|
-
case "unreachable": {
|
|
2763
|
-
const conds = [];
|
|
2764
|
-
for (const place of property.places) {
|
|
2765
|
-
const idx = flatNetIndexOf(flat, place);
|
|
2766
|
-
if (idx >= 0) conds.push(aggregate(plan, lay, idx, cur).ge(1));
|
|
2767
|
-
}
|
|
2768
|
-
if (conds.length === 0) return ctx.Bool.val(false);
|
|
2769
|
-
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})`;
|
|
2770
2912
|
}
|
|
2913
|
+
case "mutual-exclusion":
|
|
2914
|
+
return anyPlacePresent([property.p1, property.p2]);
|
|
2915
|
+
case "unreachable":
|
|
2916
|
+
return anyPlacePresent(property.places);
|
|
2771
2917
|
case "deadlock-free":
|
|
2772
|
-
return encodeColouredDeadlock(
|
|
2918
|
+
return encodeColouredDeadlock(plan, lay, flat, sinkPlaces, envInj);
|
|
2773
2919
|
case "joined-or-dead-lettered": {
|
|
2774
|
-
const
|
|
2775
|
-
if (
|
|
2776
|
-
const deadlock = encodeColouredDeadlock(
|
|
2777
|
-
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))`;
|
|
2778
2924
|
}
|
|
2779
2925
|
}
|
|
2780
2926
|
}
|
|
2781
|
-
function
|
|
2782
|
-
if (xs.length === 0) return ctx.Bool.val(true);
|
|
2783
|
-
let r = xs[0];
|
|
2784
|
-
for (let i = 1; i < xs.length; i++) r = ctx.And(r, xs[i]);
|
|
2785
|
-
return r;
|
|
2786
|
-
}
|
|
2787
|
-
function orAll(ctx, xs) {
|
|
2788
|
-
if (xs.length === 0) return ctx.Bool.val(false);
|
|
2789
|
-
let r = xs[0];
|
|
2790
|
-
for (let i = 1; i < xs.length; i++) r = ctx.Or(r, xs[i]);
|
|
2791
|
-
return r;
|
|
2792
|
-
}
|
|
2793
|
-
function injectedEnvIndices2(flat) {
|
|
2794
|
-
const out = /* @__PURE__ */ new Map();
|
|
2795
|
-
for (const [name, bound] of flat.environmentInjection) {
|
|
2796
|
-
const idx = flat.placeIndex.get(name);
|
|
2797
|
-
if (idx != null) out.set(idx, bound);
|
|
2798
|
-
}
|
|
2799
|
-
return out;
|
|
2800
|
-
}
|
|
2801
|
-
function uncolouredDisable(ft, lay, plan, envInj) {
|
|
2802
|
-
const reasons = [];
|
|
2927
|
+
function uncolouredDisable(ft, lay, plan, envInj, reasons) {
|
|
2803
2928
|
let permanentlyDisabled = false;
|
|
2804
2929
|
const P = ft.preVector.length;
|
|
2805
2930
|
for (let i = 0; i < P; i++) {
|
|
2806
2931
|
if (plan.isColoured[i] || ft.preVector[i] === 0) continue;
|
|
2807
2932
|
if (envInj.has(i)) {
|
|
2808
2933
|
const bound = envInj.get(i);
|
|
2809
|
-
if (bound
|
|
2934
|
+
if (bound != null && ft.preVector[i] > bound) permanentlyDisabled = true;
|
|
2810
2935
|
continue;
|
|
2811
2936
|
}
|
|
2812
|
-
reasons.push(lay.cur[lay.colUnc[i]]
|
|
2813
|
-
}
|
|
2814
|
-
for (const inh of ft.inhibitorPlaces) {
|
|
2815
|
-
reasons.push(lay.cur[lay.colUnc[inh]].gt(0));
|
|
2937
|
+
reasons.push(`(< ${lay.cur[lay.colUnc[i]]} ${ft.preVector[i]})`);
|
|
2816
2938
|
}
|
|
2939
|
+
for (const inh of ft.inhibitorPlaces) reasons.push(`(> ${lay.cur[lay.colUnc[inh]]} 0)`);
|
|
2817
2940
|
for (const rd of ft.readPlaces) {
|
|
2818
2941
|
if (envInj.has(rd)) {
|
|
2819
2942
|
const bound = envInj.get(rd);
|
|
2820
|
-
if (bound
|
|
2943
|
+
if (bound != null && bound < 1) permanentlyDisabled = true;
|
|
2821
2944
|
continue;
|
|
2822
2945
|
}
|
|
2823
|
-
reasons.push(lay.cur[lay.colUnc[rd]]
|
|
2946
|
+
reasons.push(`(< ${lay.cur[lay.colUnc[rd]]} 1)`);
|
|
2824
2947
|
}
|
|
2825
|
-
return
|
|
2948
|
+
return permanentlyDisabled;
|
|
2826
2949
|
}
|
|
2827
|
-
function colouredDisabledTerm(
|
|
2950
|
+
function colouredDisabledTerm(cls, plan, lay) {
|
|
2828
2951
|
const k = plan.k;
|
|
2952
|
+
if (k === 0) {
|
|
2953
|
+
return cls.kind === "untouched" ? null : "true";
|
|
2954
|
+
}
|
|
2829
2955
|
switch (cls.kind) {
|
|
2830
2956
|
case "untouched":
|
|
2831
2957
|
return null;
|
|
2832
2958
|
case "mint": {
|
|
2833
2959
|
const perColour = [];
|
|
2834
2960
|
for (let c = 0; c < k; c++) {
|
|
2835
|
-
const present = plan.coloured.map((q) => lay.cur[lay.colCol[q][c]]
|
|
2836
|
-
perColour.push(
|
|
2961
|
+
const present = plan.coloured.map((q) => `(>= ${lay.cur[lay.colCol[q][c]]} 1)`);
|
|
2962
|
+
perColour.push(`(or ${present.join(" ")})`);
|
|
2837
2963
|
}
|
|
2838
|
-
return
|
|
2964
|
+
return `(and ${perColour.join(" ")})`;
|
|
2839
2965
|
}
|
|
2840
2966
|
case "join": {
|
|
2841
2967
|
const perColour = [];
|
|
2842
2968
|
for (let c = 0; c < k; c++) {
|
|
2843
|
-
const missing = cls.colouredIn.map((i) => lay.cur[lay.colCol[i][c]]
|
|
2844
|
-
perColour.push(
|
|
2969
|
+
const missing = cls.colouredIn.map((i) => `(= ${lay.cur[lay.colCol[i][c]]} 0)`);
|
|
2970
|
+
perColour.push(`(or ${missing.join(" ")})`);
|
|
2845
2971
|
}
|
|
2846
|
-
return
|
|
2972
|
+
return `(and ${perColour.join(" ")})`;
|
|
2847
2973
|
}
|
|
2848
2974
|
case "consume": {
|
|
2849
2975
|
const perColour = [];
|
|
2850
|
-
for (let c = 0; c < k; c++) {
|
|
2851
|
-
|
|
2852
|
-
}
|
|
2853
|
-
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(" ")})`;
|
|
2854
2978
|
}
|
|
2855
2979
|
}
|
|
2856
2980
|
}
|
|
2857
|
-
function encodeColouredDeadlock(
|
|
2858
|
-
const envInj = injectedEnvIndices2(flat);
|
|
2981
|
+
function encodeColouredDeadlock(plan, lay, flat, sinkPlaces, envInj) {
|
|
2859
2982
|
const disabledConditions = [];
|
|
2860
2983
|
for (let ti = 0; ti < plan.classes.length; ti++) {
|
|
2861
2984
|
const cls = plan.classes[ti];
|
|
2862
2985
|
const ft = flat.transitions[ti];
|
|
2863
|
-
const
|
|
2986
|
+
const reasons = [];
|
|
2987
|
+
const permanentlyDisabled = uncolouredDisable(ft, lay, plan, envInj, reasons);
|
|
2864
2988
|
if (permanentlyDisabled) {
|
|
2865
|
-
disabledConditions.push(
|
|
2989
|
+
disabledConditions.push("true");
|
|
2866
2990
|
continue;
|
|
2867
2991
|
}
|
|
2868
|
-
const term = colouredDisabledTerm(
|
|
2869
|
-
if (term
|
|
2870
|
-
if (reasons.length === 0)
|
|
2871
|
-
|
|
2872
|
-
}
|
|
2873
|
-
disabledConditions.push(reasons.length === 1 ? reasons[0] : orAll(ctx, reasons));
|
|
2874
|
-
}
|
|
2875
|
-
const sinkIndices = /* @__PURE__ */ new Set();
|
|
2876
|
-
for (const sink of sinkPlaces) {
|
|
2877
|
-
const idx = flatNetIndexOf(flat, sink);
|
|
2878
|
-
if (idx >= 0) sinkIndices.add(idx);
|
|
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(" ")})`);
|
|
2879
2996
|
}
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
for (let pid = 0; pid < flat.places.length; pid++) {
|
|
2883
|
-
if (sinkIndices.has(pid)) continue;
|
|
2884
|
-
nonSink.push(aggregate(plan, lay, pid, lay.cur).ge(1));
|
|
2885
|
-
}
|
|
2886
|
-
if (nonSink.length > 0) {
|
|
2887
|
-
disabledConditions.push(orAll(ctx, nonSink));
|
|
2888
|
-
}
|
|
2997
|
+
for (const pid of indexOrdered(flat, sinkPlaces)) {
|
|
2998
|
+
disabledConditions.push(`(= ${aggregate(plan, lay, pid, lay.cur)} 0)`);
|
|
2889
2999
|
}
|
|
2890
|
-
return
|
|
3000
|
+
return disabledConditions.length === 0 ? "true" : `(and ${disabledConditions.join(" ")})`;
|
|
2891
3001
|
}
|
|
2892
3002
|
|
|
2893
3003
|
// src/verification/analysis/name-fragment.ts
|
|
@@ -3066,13 +3176,21 @@ function compareNumberArrays(a, b) {
|
|
|
3066
3176
|
var NameStateClass = class {
|
|
3067
3177
|
base;
|
|
3068
3178
|
names;
|
|
3069
|
-
key
|
|
3070
|
-
|
|
3179
|
+
/** The symmetry-canonical name-partition key (the name layer's intern key). */
|
|
3180
|
+
nameKey;
|
|
3181
|
+
constructor(base, names, colouredOrder, nameKey) {
|
|
3071
3182
|
this.base = base;
|
|
3072
3183
|
this.names = names;
|
|
3073
|
-
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}`;
|
|
3074
3189
|
}
|
|
3075
3190
|
};
|
|
3191
|
+
function baseKeyOf(base) {
|
|
3192
|
+
return `${base.marking.toString()}|${base.firingDomain.toString()}`;
|
|
3193
|
+
}
|
|
3076
3194
|
|
|
3077
3195
|
// src/verification/analysis/name-state-class-graph.ts
|
|
3078
3196
|
var NameStateClassGraph = class _NameStateClassGraph {
|
|
@@ -3101,9 +3219,16 @@ var NameStateClassGraph = class _NameStateClassGraph {
|
|
|
3101
3219
|
}
|
|
3102
3220
|
const graph = new _NameStateClassGraph();
|
|
3103
3221
|
const base0 = initialStateClass(net, initialMarking, envPlaces, envMode);
|
|
3104
|
-
const
|
|
3222
|
+
const baseIntern = /* @__PURE__ */ new Map();
|
|
3223
|
+
const nameIntern = /* @__PURE__ */ new Map();
|
|
3105
3224
|
const indexOf = /* @__PURE__ */ new Map();
|
|
3106
|
-
|
|
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
|
+
);
|
|
3107
3232
|
const sym = { next: 0 };
|
|
3108
3233
|
const queue = [0];
|
|
3109
3234
|
while (queue.length > 0) {
|
|
@@ -3132,12 +3257,18 @@ var NameStateClassGraph = class _NameStateClassGraph {
|
|
|
3132
3257
|
const baseSucc = computeSuccessor(net, current.base, vt, envPlaces, envMode);
|
|
3133
3258
|
if (baseSucc === null || baseSucc.isEmpty()) continue;
|
|
3134
3259
|
const nameSuccs = nameSuccessors(role, current.names, vt.outputPlaces, fragment, sym);
|
|
3260
|
+
const shared = internBase(baseIntern, baseSucc);
|
|
3135
3261
|
for (const nm of nameSuccs) {
|
|
3136
|
-
const
|
|
3137
|
-
|
|
3262
|
+
const sharedNames = internNames(nameIntern, nm, fragment.colouredOrder);
|
|
3263
|
+
const id = classId(shared.id, sharedNames.id);
|
|
3264
|
+
let toIdx = indexOf.get(id);
|
|
3138
3265
|
if (toIdx === void 0) {
|
|
3139
3266
|
toIdx = graph.classes.length;
|
|
3140
|
-
graph.pushClass(
|
|
3267
|
+
graph.pushClass(
|
|
3268
|
+
new NameStateClass(shared.base, sharedNames.names, fragment.colouredOrder, sharedNames.nameKey),
|
|
3269
|
+
id,
|
|
3270
|
+
indexOf
|
|
3271
|
+
);
|
|
3141
3272
|
queue.push(toIdx);
|
|
3142
3273
|
}
|
|
3143
3274
|
graph.addEdge(curIdx, toIdx, transition.name);
|
|
@@ -3147,17 +3278,38 @@ var NameStateClassGraph = class _NameStateClassGraph {
|
|
|
3147
3278
|
}
|
|
3148
3279
|
return graph;
|
|
3149
3280
|
}
|
|
3150
|
-
pushClass(c, indexOf) {
|
|
3281
|
+
pushClass(c, id, indexOf) {
|
|
3151
3282
|
const idx = this.classes.length;
|
|
3152
3283
|
this.classes.push(c);
|
|
3153
3284
|
this._successors.push([]);
|
|
3154
|
-
indexOf.set(
|
|
3285
|
+
indexOf.set(id, idx);
|
|
3155
3286
|
}
|
|
3156
3287
|
addEdge(from, to, name) {
|
|
3157
3288
|
this.edges.push({ from, to, transitionName: name });
|
|
3158
3289
|
this._successors[from].push(to);
|
|
3159
3290
|
}
|
|
3160
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
|
+
}
|
|
3161
3313
|
var READY_EPS = 1e-9;
|
|
3162
3314
|
function priorityDominated(l, idxL, enabled, readyEarliest, marking, names, fragment) {
|
|
3163
3315
|
return enabled.some(
|
|
@@ -3376,10 +3528,12 @@ function counterexamplePath(scg, target) {
|
|
|
3376
3528
|
}
|
|
3377
3529
|
|
|
3378
3530
|
// src/verification/smt-verifier.ts
|
|
3531
|
+
var IGNORE_MODE_VACUITY_REASON = "environment places present but not modeled (mode=ignore); a proof would be vacuous \u2014 use alwaysAvailable() or bounded(k) to model external injection";
|
|
3379
3532
|
var SmtVerifier = class _SmtVerifier {
|
|
3380
3533
|
constructor(net) {
|
|
3381
3534
|
this.net = net;
|
|
3382
3535
|
}
|
|
3536
|
+
net;
|
|
3383
3537
|
_initialMarking = MarkingState.empty();
|
|
3384
3538
|
_property = deadlockFree();
|
|
3385
3539
|
_environmentPlaces = /* @__PURE__ */ new Set();
|
|
@@ -3389,6 +3543,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3389
3543
|
_timeoutMs = 6e4;
|
|
3390
3544
|
_certificateCheck = true;
|
|
3391
3545
|
_counterexampleReplay = true;
|
|
3546
|
+
_semiflowInvariants = false;
|
|
3392
3547
|
_nuMaxClasses = 1e5;
|
|
3393
3548
|
_fragmentMode = "base";
|
|
3394
3549
|
_carrierPlaces = /* @__PURE__ */ new Set();
|
|
@@ -3470,6 +3625,43 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3470
3625
|
this._counterexampleReplay = enabled;
|
|
3471
3626
|
return this;
|
|
3472
3627
|
}
|
|
3628
|
+
/**
|
|
3629
|
+
* Also hands the validated **P-semiflows** to the encoders as invariants
|
|
3630
|
+
* (VER-007; default: disabled — the encoders then see only the null-space basis).
|
|
3631
|
+
*
|
|
3632
|
+
* Every validated semiflow is a conservation law in its own right (`y >= 0`,
|
|
3633
|
+
* `y·C = 0`, `y·M0` exact, zero weight on every reset / consume-all place), and
|
|
3634
|
+
* the Farkas enumeration returns the *minimal* laws of the net. The null-space
|
|
3635
|
+
* basis the encoders get by default is one basis of many: elimination hands back
|
|
3636
|
+
* mixed-sign rows (discarded as not semi-positive) or rows that fold a reset place
|
|
3637
|
+
* into a chain whose other combinations avoid it (dropped by the H1 guard). On a
|
|
3638
|
+
* net with a few reset arcs that can lose every law of the chains those arcs
|
|
3639
|
+
* touch, and without them IC3 has to rediscover the conservation of each chain —
|
|
3640
|
+
* on a ~100-place net it does not within any practical budget.
|
|
3641
|
+
*
|
|
3642
|
+
* **Turn this on if the net has any `all()` / `atLeast(n)` or reset arc on a busy
|
|
3643
|
+
* place** — draining an input queue is the everyday case. Every basis row whose
|
|
3644
|
+
* support touches such a place fails the H1 guard and is dropped, so the encoders
|
|
3645
|
+
* run on a deficient invariant set and nothing in the report says a law is missing
|
|
3646
|
+
* beyond the `Dropped` lines.
|
|
3647
|
+
*
|
|
3648
|
+
* This reaches the **name-coloured** encoder (NU-050) as well as the flat one, and
|
|
3649
|
+
* it matters most there. On a 113-place ν-net, whole-net deadlock-freedom went from
|
|
3650
|
+
* `unknown` after 50 minutes to `proven` in about 15 seconds with this option as the
|
|
3651
|
+
* only change; on the flat path, reachability-safety queries that timed out at 120 s
|
|
3652
|
+
* close in about a second.
|
|
3653
|
+
*
|
|
3654
|
+
* Soundness is unchanged: the semiflows pass the same exact re-validation as the
|
|
3655
|
+
* basis rows, the union is pure strengthening (`Semiflow.lean`,
|
|
3656
|
+
* `semiflow_union_sound`), and the certificate check re-proves the strengthened
|
|
3657
|
+
* invariant — that check is flat-path only, so a coloured `proven` reports
|
|
3658
|
+
* `Certificate check: not applicable (name-coloured encoding)`. Off by default so
|
|
3659
|
+
* reports stay byte-equal.
|
|
3660
|
+
*/
|
|
3661
|
+
semiflowInvariants(enabled) {
|
|
3662
|
+
this._semiflowInvariants = enabled;
|
|
3663
|
+
return this;
|
|
3664
|
+
}
|
|
3473
3665
|
/**
|
|
3474
3666
|
* Sets the class-count cap for the ν-aware state-class-graph analysis (NU-050,
|
|
3475
3667
|
* Route B). When the symbolic name-aware graph would exceed this, the analysis
|
|
@@ -3524,6 +3716,91 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3524
3716
|
this._prioritySemantics = semantics;
|
|
3525
3717
|
return this;
|
|
3526
3718
|
}
|
|
3719
|
+
/**
|
|
3720
|
+
* The name-coloured plan and its encoding, or a null plan when the net is outside
|
|
3721
|
+
* the fragment (NU-050) and a null encoding when the property names a place the net
|
|
3722
|
+
* does not resolve.
|
|
3723
|
+
*
|
|
3724
|
+
* {@link verify} and {@link encodeScripts} share this deliberately. They used to
|
|
3725
|
+
* invoke `buildColouredPlan` and `encodeColoured` separately, so handing the encoder
|
|
3726
|
+
* the wrong one of the two lists changed only one of them — and the script-parity
|
|
3727
|
+
* goldens are generated from `encodeScripts`. Unifying the invocation closes that. It
|
|
3728
|
+
* does not make the two paths identical: each still computes its own invariant and
|
|
3729
|
+
* semiflow lists, so they can still drift through the arguments rather than the call.
|
|
3730
|
+
*
|
|
3731
|
+
* `invariants` is what the encoder conjoins into every rule body (the null-space
|
|
3732
|
+
* basis, unioned with the semiflows when VER-007 is enabled); `semiflows` sets the
|
|
3733
|
+
* colour-slot bound k (NU-053). They are not the same list.
|
|
3734
|
+
*/
|
|
3735
|
+
colouredAttempt(flatNet, invariants, semiflows) {
|
|
3736
|
+
const hasMatch = [...this.net.transitions].some((t) => t.matchSpec !== null);
|
|
3737
|
+
const nuBounded = this._budgetPlaces.size > 0;
|
|
3738
|
+
if (!hasMatch || !nuBounded) return { plan: null, encoding: null };
|
|
3739
|
+
const plan = buildColouredPlan(
|
|
3740
|
+
this.net,
|
|
3741
|
+
flatNet,
|
|
3742
|
+
this._initialMarking,
|
|
3743
|
+
this._budgetPlaces,
|
|
3744
|
+
this._fragmentMode,
|
|
3745
|
+
this._carrierPlaces,
|
|
3746
|
+
semiflows
|
|
3747
|
+
);
|
|
3748
|
+
if (plan == null) return { plan: null, encoding: null };
|
|
3749
|
+
return {
|
|
3750
|
+
plan,
|
|
3751
|
+
encoding: encodeColoured(
|
|
3752
|
+
plan,
|
|
3753
|
+
flatNet,
|
|
3754
|
+
this._initialMarking,
|
|
3755
|
+
this._property,
|
|
3756
|
+
invariants,
|
|
3757
|
+
this._sinkPlaces
|
|
3758
|
+
)
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
/**
|
|
3762
|
+
* The SMT-LIB2 scripts {@link verify} would send to z3 for this configuration,
|
|
3763
|
+
* without running a solver (VER-013 AC1): the HORN query (flat, or name-coloured
|
|
3764
|
+
* when a declared budget puts the net on Route A's exact encoding) and, for the
|
|
3765
|
+
* flat encoding, the certificate-check script built around
|
|
3766
|
+
* {@link placeholderCertificate}. This is what the cross-language golden tests diff
|
|
3767
|
+
* byte for byte. Route B, the structural pre-check and the unresolved-place
|
|
3768
|
+
* refusal are bypassed: it is what Route A encodes.
|
|
3769
|
+
*/
|
|
3770
|
+
encodeScripts() {
|
|
3771
|
+
requireOutputProducingActions(this.net);
|
|
3772
|
+
const flatNet = flatten(this.net, this._environmentPlaces, this._environmentMode);
|
|
3773
|
+
const matrix = IncidenceMatrix.from(flatNet);
|
|
3774
|
+
const { valid: basis } = validateInvariantsExact(
|
|
3775
|
+
matrix,
|
|
3776
|
+
computePInvariants(matrix, flatNet, this._initialMarking),
|
|
3777
|
+
flatNet,
|
|
3778
|
+
this._initialMarking
|
|
3779
|
+
);
|
|
3780
|
+
const { valid: semiflows } = validateInvariantsExact(
|
|
3781
|
+
matrix,
|
|
3782
|
+
computePSemiflows(matrix, flatNet, this._initialMarking),
|
|
3783
|
+
flatNet,
|
|
3784
|
+
this._initialMarking
|
|
3785
|
+
);
|
|
3786
|
+
let invariants = basis;
|
|
3787
|
+
if (this._semiflowInvariants) invariants = strengthenWithSemiflows(basis, semiflows).invariants;
|
|
3788
|
+
invariants = canonicalInvariantOrder(invariants);
|
|
3789
|
+
const attempt = this.colouredAttempt(flatNet, invariants, semiflows);
|
|
3790
|
+
if (attempt.encoding != null) {
|
|
3791
|
+
return { horn: attempt.encoding.smt2, certificate: null, coloured: true };
|
|
3792
|
+
}
|
|
3793
|
+
const horn = encode(flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces, this._counterexampleReplay).smt2;
|
|
3794
|
+
const certificate = vcScript(
|
|
3795
|
+
placeholderCertificate(flatNet.places.length),
|
|
3796
|
+
flatNet,
|
|
3797
|
+
this._initialMarking,
|
|
3798
|
+
this._property,
|
|
3799
|
+
this._sinkPlaces,
|
|
3800
|
+
invariants
|
|
3801
|
+
);
|
|
3802
|
+
return { horn, certificate, coloured: false };
|
|
3803
|
+
}
|
|
3527
3804
|
/**
|
|
3528
3805
|
* Runs the verification pipeline.
|
|
3529
3806
|
*
|
|
@@ -3562,8 +3839,13 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3562
3839
|
if (outcome.transitions.length > 0) {
|
|
3563
3840
|
report.push(` Counterexample trace: ${outcome.trace.length} states, ${outcome.transitions.length} transitions`);
|
|
3564
3841
|
}
|
|
3842
|
+
let routeBVerdict = outcome.verdict;
|
|
3843
|
+
if (routeBVerdict.type === "proven" && this._environmentPlaces.size > 0 && this._environmentMode.type === "ignore") {
|
|
3844
|
+
report.push(` Downgraded to UNKNOWN: ${IGNORE_MODE_VACUITY_REASON}`);
|
|
3845
|
+
routeBVerdict = { type: "unknown", reason: IGNORE_MODE_VACUITY_REASON };
|
|
3846
|
+
}
|
|
3565
3847
|
return buildResult(
|
|
3566
|
-
|
|
3848
|
+
routeBVerdict,
|
|
3567
3849
|
report.join("\n"),
|
|
3568
3850
|
[],
|
|
3569
3851
|
[],
|
|
@@ -3630,7 +3912,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3630
3912
|
}
|
|
3631
3913
|
report.push("Phase 3: Computing P-invariants...");
|
|
3632
3914
|
const matrix = IncidenceMatrix.from(flatNet);
|
|
3633
|
-
const { valid:
|
|
3915
|
+
const { valid: basisInvariants, dropped: droppedInvariants } = validateInvariantsExact(
|
|
3634
3916
|
matrix,
|
|
3635
3917
|
computePInvariants(matrix, flatNet, this._initialMarking),
|
|
3636
3918
|
flatNet,
|
|
@@ -3642,7 +3924,14 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3642
3924
|
flatNet,
|
|
3643
3925
|
this._initialMarking
|
|
3644
3926
|
);
|
|
3645
|
-
report.push(` Found: ${
|
|
3927
|
+
report.push(` Found: ${basisInvariants.length} P-invariant(s)`);
|
|
3928
|
+
let invariants = basisInvariants;
|
|
3929
|
+
if (this._semiflowInvariants) {
|
|
3930
|
+
const { invariants: strengthened, added } = strengthenWithSemiflows(basisInvariants, semiflows);
|
|
3931
|
+
invariants = strengthened;
|
|
3932
|
+
report.push(` Semiflows encoded as invariants: ${added}`);
|
|
3933
|
+
}
|
|
3934
|
+
invariants = canonicalInvariantOrder(invariants);
|
|
3646
3935
|
const structurallyBounded = isCoveredByInvariants(invariants, flatNet.places.length);
|
|
3647
3936
|
report.push(` Structurally bounded: ${structurallyBounded ? "YES" : "NO"}`);
|
|
3648
3937
|
for (const inv of invariants) {
|
|
@@ -3662,284 +3951,210 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3662
3951
|
}
|
|
3663
3952
|
report.push("");
|
|
3664
3953
|
report.push("Phase 4: IC3/PDR verification via Z3 Spacer...");
|
|
3665
|
-
const
|
|
3666
|
-
|
|
3667
|
-
flatNet,
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
semiflows
|
|
3673
|
-
) : null;
|
|
3674
|
-
let runner;
|
|
3954
|
+
const stats = {
|
|
3955
|
+
places: flatNet.places.length,
|
|
3956
|
+
transitions: flatNet.transitions.length,
|
|
3957
|
+
invariantsFound: invariants.length,
|
|
3958
|
+
structuralResult: structResultStr
|
|
3959
|
+
};
|
|
3960
|
+
let solver;
|
|
3675
3961
|
try {
|
|
3676
|
-
|
|
3962
|
+
solver = resolveZ3();
|
|
3677
3963
|
} catch (e) {
|
|
3678
|
-
|
|
3964
|
+
const reason = e instanceof Z3Unavailable ? e.message : String(e?.message ?? e);
|
|
3965
|
+
report.push(` Solver: z3 unavailable (${reason})`);
|
|
3966
|
+
report.push(` Status: UNKNOWN (${reason})
|
|
3679
3967
|
`);
|
|
3680
3968
|
report.push("=== RESULT ===\n");
|
|
3681
|
-
report.push(`UNKNOWN:
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3969
|
+
report.push(`UNKNOWN: Could not determine ${propDesc}`);
|
|
3970
|
+
report.push(` Reason: ${reason}`);
|
|
3971
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
3972
|
+
}
|
|
3973
|
+
report.push(` Solver: z3 ${formatZ3Version(solver.version)}`);
|
|
3974
|
+
const colouredAttempt = this.colouredAttempt(flatNet, invariants, semiflows);
|
|
3975
|
+
const colouredPlan = colouredAttempt.plan;
|
|
3976
|
+
let encoding;
|
|
3977
|
+
if (colouredPlan != null) {
|
|
3978
|
+
report.push(
|
|
3979
|
+
` \u03BD-encoding: name-coloured (exact within budget k=${colouredPlan.k}; ${colouredPlan.coloured.length} coloured place(s))`
|
|
3691
3980
|
);
|
|
3981
|
+
const coloured = colouredAttempt.encoding;
|
|
3982
|
+
if (coloured == null) {
|
|
3983
|
+
const reason = "property names a place that does not resolve in the net; refusing to certify (the encoding would be vacuously proven)";
|
|
3984
|
+
report.push(" Status: UNKNOWN (unresolved property place)\n");
|
|
3985
|
+
report.push("=== RESULT ===\n");
|
|
3986
|
+
report.push(`UNKNOWN: ${reason}`);
|
|
3987
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
3988
|
+
}
|
|
3989
|
+
encoding = coloured;
|
|
3990
|
+
} else {
|
|
3991
|
+
const unresolved = unresolvedPropertyPlace(flatNet, this._property);
|
|
3992
|
+
if (unresolved != null) {
|
|
3993
|
+
const reason = `property names a place that does not resolve in the net ('${unresolved}'); refusing to certify (the encoding would be vacuously proven)`;
|
|
3994
|
+
report.push(" Status: UNKNOWN (unresolved property place)\n");
|
|
3995
|
+
report.push("=== RESULT ===\n");
|
|
3996
|
+
report.push(`UNKNOWN: ${reason}`);
|
|
3997
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
3998
|
+
}
|
|
3999
|
+
encoding = encode(flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces, this._counterexampleReplay);
|
|
3692
4000
|
}
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
4001
|
+
const queryResult = await runZ3Spacer(
|
|
4002
|
+
solver,
|
|
4003
|
+
this._timeoutMs,
|
|
4004
|
+
encoding.smt2,
|
|
4005
|
+
colouredPlan != null ? "horn-coloured" : "horn"
|
|
4006
|
+
);
|
|
4007
|
+
switch (queryResult.type) {
|
|
4008
|
+
case "proven": {
|
|
4009
|
+
if (this._environmentPlaces.size > 0 && this._environmentMode.type === "ignore") {
|
|
4010
|
+
const reason = IGNORE_MODE_VACUITY_REASON;
|
|
4011
|
+
report.push(` Status: UNSAT, but vacuous under ignore mode
|
|
4012
|
+
`);
|
|
3703
4013
|
report.push("=== RESULT ===\n");
|
|
3704
4014
|
report.push(`UNKNOWN: ${reason}`);
|
|
3705
|
-
return buildResult(
|
|
3706
|
-
|
|
3707
|
-
|
|
4015
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
4016
|
+
}
|
|
4017
|
+
report.push(" Status: UNSAT (property holds)");
|
|
4018
|
+
if (colouredPlan != null) {
|
|
4019
|
+
report.push(" Certificate check: not applicable (name-coloured encoding)");
|
|
4020
|
+
} else if (!this._certificateCheck) {
|
|
4021
|
+
report.push(" Certificate check: not applicable (disabled)");
|
|
4022
|
+
} else {
|
|
4023
|
+
const certificate = await checkCertificate(
|
|
4024
|
+
queryResult.invariantFormula,
|
|
4025
|
+
flatNet,
|
|
4026
|
+
this._initialMarking,
|
|
4027
|
+
this._property,
|
|
3708
4028
|
invariants,
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
performance.now() - start,
|
|
3713
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
4029
|
+
this._sinkPlaces,
|
|
4030
|
+
solver,
|
|
4031
|
+
this._timeoutMs
|
|
3714
4032
|
);
|
|
4033
|
+
const reason = certificateDowngradeReason(certificate);
|
|
4034
|
+
if (reason != null) {
|
|
4035
|
+
report.push(" Certificate check: FAILED");
|
|
4036
|
+
if (certificate.type !== "passed" && certificate.invariant != null) {
|
|
4037
|
+
report.push(" Uncertified invariant:");
|
|
4038
|
+
for (const line of certificate.invariant.split("\n")) report.push(` ${line}`);
|
|
4039
|
+
}
|
|
4040
|
+
report.push("");
|
|
4041
|
+
report.push("=== RESULT ===\n");
|
|
4042
|
+
report.push(`UNKNOWN: ${reason}`);
|
|
4043
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
4044
|
+
}
|
|
4045
|
+
report.push(" Certificate check: PASSED (init, consecution, safety)");
|
|
4046
|
+
}
|
|
4047
|
+
report.push("");
|
|
4048
|
+
const formula = queryResult.invariantFormula;
|
|
4049
|
+
const discoveredInvariants = formula != null ? [formula] : [];
|
|
4050
|
+
if (formula != null) {
|
|
4051
|
+
report.push("Phase 5: Inductive invariant (discovered by IC3)");
|
|
4052
|
+
report.push(" Spacer synthesized:");
|
|
4053
|
+
for (const line of formula.split("\n")) report.push(` ${line}`);
|
|
4054
|
+
report.push(" This formula is INDUCTIVE: preserved by all transitions.");
|
|
4055
|
+
report.push("");
|
|
3715
4056
|
}
|
|
3716
|
-
|
|
3717
|
-
|
|
4057
|
+
report.push("=== RESULT ===\n");
|
|
4058
|
+
report.push(`PROVEN (IC3/PDR): ${propDesc}`);
|
|
4059
|
+
report.push(" Z3 Spacer proved no reachable state violates the property.");
|
|
4060
|
+
report.push(" NOTE: Verification ignores timing constraints.");
|
|
4061
|
+
report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
|
|
4062
|
+
return this.applyNuGuard(buildResult(
|
|
4063
|
+
{ type: "proven", method: "IC3/PDR", inductiveInvariant: formula },
|
|
4064
|
+
report.join("\n"),
|
|
4065
|
+
invariants,
|
|
4066
|
+
discoveredInvariants,
|
|
4067
|
+
[],
|
|
4068
|
+
[],
|
|
4069
|
+
performance.now() - start,
|
|
4070
|
+
stats
|
|
4071
|
+
), hasMatch, nuBounded, colouredPlan != null);
|
|
3718
4072
|
}
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
4073
|
+
case "violated": {
|
|
4074
|
+
report.push(" Status: SAT (counterexample found)\n");
|
|
4075
|
+
const decoded = decode(queryResult.answer, flatNet);
|
|
4076
|
+
if (decoded.note != null) report.push(` Counterexample decoding: ${decoded.note}`);
|
|
4077
|
+
let confirmed = null;
|
|
4078
|
+
let trace = [...decoded.states];
|
|
4079
|
+
let transitions = [];
|
|
4080
|
+
let replayed = false;
|
|
4081
|
+
if (colouredPlan == null && this._counterexampleReplay) {
|
|
4082
|
+
const assessment = assessCounterexample(
|
|
4083
|
+
flatNet,
|
|
4084
|
+
this._initialMarking,
|
|
4085
|
+
decoded.states,
|
|
4086
|
+
this._property,
|
|
4087
|
+
this._sinkPlaces
|
|
4088
|
+
);
|
|
4089
|
+
if (assessment.kind === "confirmed") {
|
|
4090
|
+
confirmed = true;
|
|
4091
|
+
replayed = true;
|
|
4092
|
+
trace = assessment.trace;
|
|
4093
|
+
transitions = assessment.firings;
|
|
4094
|
+
report.push(" Counterexample replay: CONFIRMED (abstract chain M0 -> bad re-executed)");
|
|
4095
|
+
} else if (assessment.kind === "unconfirmed") {
|
|
4096
|
+
confirmed = false;
|
|
4097
|
+
report.push(` Counterexample replay: UNCONFIRMED (${assessment.note})`);
|
|
4098
|
+
report.push(" The verdict rests on Spacer's answer.");
|
|
4099
|
+
} else {
|
|
4100
|
+
report.push(" Counterexample replay: FAILED");
|
|
4101
|
+
report.push(` Decoded states (order-free set, ${decoded.states.size}):`);
|
|
4102
|
+
for (const m of decoded.states) report.push(` ${m}`);
|
|
4103
|
+
report.push(` Raw Z3 answer: ${truncate(queryResult.answer, 2e3)}`);
|
|
4104
|
+
report.push("");
|
|
3726
4105
|
report.push("=== RESULT ===\n");
|
|
3727
|
-
report.push(`UNKNOWN: ${reason}`);
|
|
4106
|
+
report.push(`UNKNOWN: ${assessment.reason}`);
|
|
3728
4107
|
return buildResult(
|
|
3729
|
-
{ type: "unknown", reason },
|
|
4108
|
+
{ type: "unknown", reason: assessment.reason },
|
|
3730
4109
|
report.join("\n"),
|
|
3731
4110
|
invariants,
|
|
3732
4111
|
[],
|
|
3733
4112
|
[],
|
|
3734
4113
|
[],
|
|
3735
4114
|
performance.now() - start,
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
}
|
|
3739
|
-
report.push(" Status: UNSAT (property holds)");
|
|
3740
|
-
if (colouredPlan != null) {
|
|
3741
|
-
report.push(" Certificate check: not applicable (name-coloured encoding)");
|
|
3742
|
-
} else if (!this._certificateCheck) {
|
|
3743
|
-
report.push(" Certificate check: not applicable (disabled)");
|
|
3744
|
-
} else {
|
|
3745
|
-
const certificate = await checkCertificate(
|
|
3746
|
-
runner.ctx,
|
|
3747
|
-
queryResult.answer,
|
|
3748
|
-
flatNet,
|
|
3749
|
-
this._initialMarking,
|
|
3750
|
-
this._property,
|
|
3751
|
-
invariants,
|
|
3752
|
-
this._sinkPlaces,
|
|
3753
|
-
this._timeoutMs
|
|
3754
|
-
);
|
|
3755
|
-
const reason = certificateDowngradeReason(certificate);
|
|
3756
|
-
if (reason != null) {
|
|
3757
|
-
report.push(" Certificate check: FAILED");
|
|
3758
|
-
if (certificate.type !== "passed" && certificate.invariant != null) {
|
|
3759
|
-
report.push(` Uncertified invariant: ${substituteNames(certificate.invariant, flatNet)}`);
|
|
3760
|
-
}
|
|
3761
|
-
report.push("");
|
|
3762
|
-
report.push("=== RESULT ===\n");
|
|
3763
|
-
report.push(`UNKNOWN: ${reason}`);
|
|
3764
|
-
return buildResult(
|
|
3765
|
-
{ type: "unknown", reason },
|
|
3766
|
-
report.join("\n"),
|
|
3767
|
-
invariants,
|
|
3768
|
-
[],
|
|
3769
|
-
[],
|
|
3770
|
-
[],
|
|
3771
|
-
performance.now() - start,
|
|
3772
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3773
|
-
);
|
|
3774
|
-
}
|
|
3775
|
-
report.push(" Certificate check: PASSED (init, consecution, safety)");
|
|
3776
|
-
}
|
|
3777
|
-
report.push("");
|
|
3778
|
-
const discoveredInvariants = [];
|
|
3779
|
-
if (queryResult.invariantFormula != null) {
|
|
3780
|
-
discoveredInvariants.push(substituteNames(queryResult.invariantFormula, flatNet));
|
|
3781
|
-
}
|
|
3782
|
-
for (const level of queryResult.levelInvariants) {
|
|
3783
|
-
discoveredInvariants.push(substituteNames(level, flatNet));
|
|
3784
|
-
}
|
|
3785
|
-
if (discoveredInvariants.length > 0) {
|
|
3786
|
-
report.push("Phase 5: Inductive invariant (discovered by IC3)");
|
|
3787
|
-
report.push(` Spacer synthesized: ${discoveredInvariants[0]}`);
|
|
3788
|
-
report.push(" This formula is INDUCTIVE: preserved by all transitions.");
|
|
3789
|
-
if (discoveredInvariants.length > 1) {
|
|
3790
|
-
report.push(" Per-level clauses:");
|
|
3791
|
-
for (let i = 1; i < discoveredInvariants.length; i++) {
|
|
3792
|
-
report.push(` ${discoveredInvariants[i]}`);
|
|
3793
|
-
}
|
|
3794
|
-
}
|
|
3795
|
-
report.push("");
|
|
3796
|
-
}
|
|
3797
|
-
report.push("=== RESULT ===\n");
|
|
3798
|
-
report.push(`PROVEN (IC3/PDR): ${propDesc}`);
|
|
3799
|
-
report.push(" Z3 Spacer proved no reachable state violates the property.");
|
|
3800
|
-
report.push(" NOTE: Verification ignores timing constraints.");
|
|
3801
|
-
report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
|
|
3802
|
-
return this.applyNuGuard(buildResult(
|
|
3803
|
-
{
|
|
3804
|
-
type: "proven",
|
|
3805
|
-
method: "IC3/PDR",
|
|
3806
|
-
inductiveInvariant: queryResult.invariantFormula != null ? substituteNames(queryResult.invariantFormula, flatNet) : null
|
|
3807
|
-
},
|
|
3808
|
-
report.join("\n"),
|
|
3809
|
-
invariants,
|
|
3810
|
-
discoveredInvariants,
|
|
3811
|
-
[],
|
|
3812
|
-
[],
|
|
3813
|
-
performance.now() - start,
|
|
3814
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3815
|
-
), hasMatch, nuBounded, colouredPlan != null);
|
|
3816
|
-
}
|
|
3817
|
-
case "violated": {
|
|
3818
|
-
report.push(" Status: SAT (counterexample found)\n");
|
|
3819
|
-
const decoded = decode(runner.ctx, queryResult.answer, flatNet);
|
|
3820
|
-
const stats = {
|
|
3821
|
-
places: flatNet.places.length,
|
|
3822
|
-
transitions: flatNet.transitions.length,
|
|
3823
|
-
invariantsFound: invariants.length,
|
|
3824
|
-
structuralResult: structResultStr
|
|
3825
|
-
};
|
|
3826
|
-
let confirmed = null;
|
|
3827
|
-
let trace = decoded.trace;
|
|
3828
|
-
let transitions = decoded.transitions;
|
|
3829
|
-
let replayed = false;
|
|
3830
|
-
if (colouredPlan == null && this._counterexampleReplay) {
|
|
3831
|
-
const assessment = assessCounterexample(
|
|
3832
|
-
flatNet,
|
|
3833
|
-
this._initialMarking,
|
|
3834
|
-
decoded.states,
|
|
3835
|
-
this._property,
|
|
3836
|
-
this._sinkPlaces
|
|
4115
|
+
stats,
|
|
4116
|
+
false
|
|
3837
4117
|
);
|
|
3838
|
-
if (assessment.kind === "confirmed") {
|
|
3839
|
-
confirmed = true;
|
|
3840
|
-
replayed = true;
|
|
3841
|
-
trace = assessment.trace;
|
|
3842
|
-
transitions = assessment.firings;
|
|
3843
|
-
report.push(" Counterexample replay: CONFIRMED (abstract chain M0 -> bad re-executed)");
|
|
3844
|
-
} else if (assessment.kind === "unconfirmed") {
|
|
3845
|
-
confirmed = false;
|
|
3846
|
-
report.push(` Counterexample replay: UNCONFIRMED (${assessment.note})`);
|
|
3847
|
-
if (decoded.failure != null) {
|
|
3848
|
-
report.push(` Decoder degradation: ${describeDecodeFailure(decoded.failure)}`);
|
|
3849
|
-
}
|
|
3850
|
-
report.push(" The verdict rests on Spacer's SAT answer.");
|
|
3851
|
-
} else {
|
|
3852
|
-
report.push(" Counterexample replay: FAILED");
|
|
3853
|
-
report.push(` Decoded states (order-free set, ${decoded.states.size}):`);
|
|
3854
|
-
for (const m of decoded.states) {
|
|
3855
|
-
report.push(` ${m}`);
|
|
3856
|
-
}
|
|
3857
|
-
if (decoded.trace.length > 0) {
|
|
3858
|
-
report.push(` Raw traversal-order trace (${decoded.trace.length} states):`);
|
|
3859
|
-
for (let i = 0; i < decoded.trace.length; i++) {
|
|
3860
|
-
report.push(` ${i}: ${decoded.trace[i]}`);
|
|
3861
|
-
}
|
|
3862
|
-
}
|
|
3863
|
-
if (decoded.failure != null) {
|
|
3864
|
-
report.push(` Decoder degradation: ${describeDecodeFailure(decoded.failure)}`);
|
|
3865
|
-
}
|
|
3866
|
-
report.push(` Raw Z3 answer: ${truncate(String(queryResult.answer), 2e3)}`);
|
|
3867
|
-
report.push("");
|
|
3868
|
-
report.push("=== RESULT ===\n");
|
|
3869
|
-
report.push(`UNKNOWN: ${assessment.reason}`);
|
|
3870
|
-
return buildResult(
|
|
3871
|
-
{ type: "unknown", reason: assessment.reason },
|
|
3872
|
-
report.join("\n"),
|
|
3873
|
-
invariants,
|
|
3874
|
-
[],
|
|
3875
|
-
[],
|
|
3876
|
-
[],
|
|
3877
|
-
performance.now() - start,
|
|
3878
|
-
stats,
|
|
3879
|
-
false
|
|
3880
|
-
);
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
3883
|
-
report.push("=== RESULT ===\n");
|
|
3884
|
-
report.push(`VIOLATED: ${propDesc}`);
|
|
3885
|
-
if (trace.length > 0) {
|
|
3886
|
-
report.push(` Counterexample trace (${replayed ? "replay order, " : ""}${trace.length} states):`);
|
|
3887
|
-
for (let i = 0; i < trace.length; i++) {
|
|
3888
|
-
report.push(` ${i}: ${trace[i]}`);
|
|
3889
|
-
}
|
|
3890
|
-
}
|
|
3891
|
-
if (transitions.length > 0) {
|
|
3892
|
-
report.push(` Firing sequence: ${transitions.join(" -> ")}`);
|
|
3893
4118
|
}
|
|
3894
|
-
report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
|
|
3895
|
-
report.push(" It may be spurious if timing constraints prevent this sequence.");
|
|
3896
|
-
return this.applyNuGuard(buildResult(
|
|
3897
|
-
{ type: "violated" },
|
|
3898
|
-
report.join("\n"),
|
|
3899
|
-
invariants,
|
|
3900
|
-
[],
|
|
3901
|
-
trace,
|
|
3902
|
-
transitions,
|
|
3903
|
-
performance.now() - start,
|
|
3904
|
-
stats,
|
|
3905
|
-
confirmed
|
|
3906
|
-
), hasMatch, nuBounded, colouredPlan != null);
|
|
3907
4119
|
}
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
report.push("
|
|
3912
|
-
report.push(`
|
|
3913
|
-
report.push(` Reason: ${queryResult.reason}`);
|
|
3914
|
-
return buildResult(
|
|
3915
|
-
{ type: "unknown", reason: queryResult.reason },
|
|
3916
|
-
report.join("\n"),
|
|
3917
|
-
invariants,
|
|
3918
|
-
[],
|
|
3919
|
-
[],
|
|
3920
|
-
[],
|
|
3921
|
-
performance.now() - start,
|
|
3922
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3923
|
-
);
|
|
4120
|
+
report.push("=== RESULT ===\n");
|
|
4121
|
+
report.push(`VIOLATED: ${propDesc}`);
|
|
4122
|
+
if (trace.length > 0) {
|
|
4123
|
+
report.push(` Counterexample trace (${replayed ? "replay order, " : "proof order, "}${trace.length} states):`);
|
|
4124
|
+
for (let i = 0; i < trace.length; i++) report.push(` ${i}: ${trace[i]}`);
|
|
3924
4125
|
}
|
|
4126
|
+
if (transitions.length > 0) report.push(` Firing sequence: ${transitions.join(" -> ")}`);
|
|
4127
|
+
report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
|
|
4128
|
+
report.push(" It may be spurious if timing constraints prevent this sequence.");
|
|
4129
|
+
return this.applyNuGuard(buildResult(
|
|
4130
|
+
{ type: "violated" },
|
|
4131
|
+
report.join("\n"),
|
|
4132
|
+
invariants,
|
|
4133
|
+
[],
|
|
4134
|
+
trace,
|
|
4135
|
+
transitions,
|
|
4136
|
+
performance.now() - start,
|
|
4137
|
+
stats,
|
|
4138
|
+
confirmed
|
|
4139
|
+
), hasMatch, nuBounded, colouredPlan != null);
|
|
3925
4140
|
}
|
|
3926
|
-
|
|
3927
|
-
|
|
4141
|
+
case "unknown": {
|
|
4142
|
+
report.push(` Status: UNKNOWN (${queryResult.reason})
|
|
3928
4143
|
`);
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
4144
|
+
report.push("=== RESULT ===\n");
|
|
4145
|
+
report.push(`UNKNOWN: Could not determine ${propDesc}`);
|
|
4146
|
+
report.push(` Reason: ${queryResult.reason}`);
|
|
4147
|
+
return buildResult(
|
|
4148
|
+
{ type: "unknown", reason: queryResult.reason },
|
|
4149
|
+
report.join("\n"),
|
|
4150
|
+
invariants,
|
|
4151
|
+
[],
|
|
4152
|
+
[],
|
|
4153
|
+
[],
|
|
4154
|
+
performance.now() - start,
|
|
4155
|
+
stats
|
|
4156
|
+
);
|
|
4157
|
+
}
|
|
3943
4158
|
}
|
|
3944
4159
|
}
|
|
3945
4160
|
/**
|
|
@@ -4040,6 +4255,12 @@ function certificateDowngradeReason(outcome) {
|
|
|
4040
4255
|
return `certificate check could not run: ${outcome.reason}; PROVEN is withheld without an independently validated certificate`;
|
|
4041
4256
|
}
|
|
4042
4257
|
}
|
|
4258
|
+
function placeholderCertificate(placeCount) {
|
|
4259
|
+
const params = [];
|
|
4260
|
+
for (let i = 0; i < placeCount; i++) params.push(`(x!${i} Int)`);
|
|
4261
|
+
return `(define-fun Reachable (${params.join(" ")}) Bool
|
|
4262
|
+
true)`;
|
|
4263
|
+
}
|
|
4043
4264
|
function downgradeToUnknown(result, reason) {
|
|
4044
4265
|
return {
|
|
4045
4266
|
...result,
|
|
@@ -4056,11 +4277,27 @@ Downgraded to UNKNOWN: ${reason}
|
|
|
4056
4277
|
function truncate(s, max) {
|
|
4057
4278
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (${s.length - max} chars truncated)`;
|
|
4058
4279
|
}
|
|
4059
|
-
function
|
|
4060
|
-
|
|
4061
|
-
|
|
4280
|
+
function unresolvedPropertyPlace(flatNet, property) {
|
|
4281
|
+
const named = (() => {
|
|
4282
|
+
switch (property.type) {
|
|
4283
|
+
case "deadlock-free":
|
|
4284
|
+
return [];
|
|
4285
|
+
case "mutual-exclusion":
|
|
4286
|
+
return [property.p1, property.p2];
|
|
4287
|
+
case "place-bound":
|
|
4288
|
+
return [property.place];
|
|
4289
|
+
case "branch-place-bound":
|
|
4290
|
+
return [property.place];
|
|
4291
|
+
case "unreachable":
|
|
4292
|
+
return [...property.places];
|
|
4293
|
+
case "joined-or-dead-lettered":
|
|
4294
|
+
return [property.pending];
|
|
4295
|
+
}
|
|
4296
|
+
})();
|
|
4297
|
+
for (const place of named) {
|
|
4298
|
+
if (!flatNet.placeIndex.has(place.name)) return place.name;
|
|
4062
4299
|
}
|
|
4063
|
-
return
|
|
4300
|
+
return null;
|
|
4064
4301
|
}
|
|
4065
4302
|
function formatInvariant(inv, flatNet) {
|
|
4066
4303
|
const parts = [];
|
|
@@ -4128,26 +4365,42 @@ export {
|
|
|
4128
4365
|
pInvariant,
|
|
4129
4366
|
pInvariantToString,
|
|
4130
4367
|
computePInvariants,
|
|
4368
|
+
strengthenWithSemiflows,
|
|
4131
4369
|
computePSemiflows,
|
|
4132
4370
|
isCoveredByInvariants,
|
|
4371
|
+
canonicalInvariantOrder,
|
|
4133
4372
|
structuralCheck,
|
|
4134
4373
|
findMinimalSiphons,
|
|
4135
4374
|
findMaximalTrapIn,
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4375
|
+
Z3_ENV,
|
|
4376
|
+
DUMP_ENV,
|
|
4377
|
+
MIN_Z3_VERSION,
|
|
4378
|
+
parseZ3Version,
|
|
4379
|
+
formatZ3Version,
|
|
4380
|
+
Z3Unavailable,
|
|
4381
|
+
Z3ProcessError,
|
|
4382
|
+
z3SolverAt,
|
|
4383
|
+
resolveZ3,
|
|
4384
|
+
z3Available,
|
|
4385
|
+
runZ3Text,
|
|
4386
|
+
runZ3Spacer,
|
|
4140
4387
|
encode,
|
|
4388
|
+
encodeStepRelationSmt2,
|
|
4141
4389
|
checkCertificate,
|
|
4390
|
+
vcScript,
|
|
4142
4391
|
DBM,
|
|
4143
4392
|
StateClass,
|
|
4144
4393
|
requireOutputProducingActions,
|
|
4145
4394
|
StateClassGraph,
|
|
4146
|
-
describeDecodeFailure,
|
|
4147
4395
|
decode,
|
|
4396
|
+
decodeStateSet,
|
|
4397
|
+
flatNetPlaceCount,
|
|
4398
|
+
flatNetTransitionCount,
|
|
4399
|
+
flatNetIndexOf,
|
|
4148
4400
|
replayCounterexample,
|
|
4149
4401
|
SmtVerifier,
|
|
4402
|
+
placeholderCertificate,
|
|
4150
4403
|
isProven,
|
|
4151
4404
|
isViolated
|
|
4152
4405
|
};
|
|
4153
|
-
//# sourceMappingURL=chunk-
|
|
4406
|
+
//# sourceMappingURL=chunk-FD3S4TZM.js.map
|