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.
@@ -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.localeCompare(b.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 (!allNonNegative) {
680
- let allNonPositive = true;
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/spacer-runner.ts
1094
- import { init } from "z3-solver";
1095
- async function createSpacerRunner(timeoutMs) {
1096
- const { Context } = await init();
1097
- const ctx = new Context("main");
1098
- const fp = new ctx.Fixedpoint();
1099
- fp.set("engine", "spacer");
1100
- if (timeoutMs > 0) {
1101
- fp.set("timeout", Math.min(timeoutMs, 2147483647));
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
- // src/verification/encoding/flat-net.ts
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 flatNetTransitionCount(net) {
1164
- return net.transitions.length;
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 flatNetIndexOf(net, place) {
1167
- return net.placeIndex.get(place.name) ?? -1;
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/smt-encoder.ts
1171
- function encode(ctx, fp, flatNet, initialMarking, property, invariants, sinkPlaces = /* @__PURE__ */ new Set()) {
1172
- const P = flatNet.places.length;
1173
- const Int = ctx.Int;
1174
- const Bool_ = ctx.Bool;
1175
- const intSort = Int.sort();
1176
- const boolSort = Bool_.sort();
1177
- const markingSorts = new Array(P).fill(intSort);
1178
- const reachable = ctx.Function.declare("Reachable", ...markingSorts, boolSort);
1179
- fp.registerRelation(reachable);
1180
- const error = ctx.Function.declare("Error", boolSort);
1181
- fp.registerRelation(error);
1182
- const m0Args = [];
1183
- for (let i = 0; i < P; i++) {
1184
- const tokens = initialMarking.tokens(flatNet.places[i]);
1185
- m0Args.push(Int.val(tokens));
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
- const initFact = reachable.call(...m0Args);
1188
- fp.addRule(initFact, "init");
1189
- for (let t = 0; t < flatNet.transitions.length; t++) {
1190
- const ft = flatNet.transitions[t];
1191
- encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P);
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
- encodeErrorRule(ctx, fp, reachable, error, flatNet, property, sinkPlaces, P);
1199
- return {
1200
- errorExpr: error.call(),
1201
- reachableDecl: reachable
1202
- };
1206
+ };
1207
+ function replySucceeded(reply) {
1208
+ return reply.exit.kind === "exited" && reply.exit.code === 0;
1203
1209
  }
1204
- function encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P) {
1205
- const Int = ctx.Int;
1206
- const mVars = [];
1207
- const mPrimeVars = [];
1208
- for (let i = 0; i < P; i++) {
1209
- mVars.push(Int.const(`m${i}`));
1210
- mPrimeVars.push(Int.const(`mp${i}`));
1211
- }
1212
- const reachBody = reachable.call(...mVars);
1213
- const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
1214
- const fireRelation = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
1215
- const nonNeg = encodeNonNegativity(ctx, mPrimeVars, P);
1216
- const invConstraints = encodeInvariantConstraints(ctx, invariants, mPrimeVars, P);
1217
- const envBounds = encodeEnvBounds(ctx, flatNet, mPrimeVars);
1218
- const body = ctx.And(reachBody, enabled, fireRelation, nonNeg, invConstraints, envBounds);
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
- return result;
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 encodeEnvBounds(ctx, flatNet, vars) {
1249
- let result = ctx.Bool.val(true);
1250
- for (const [name, bound] of flatNet.environmentBounds) {
1251
- const idx = flatNet.placeIndex.get(name);
1252
- if (idx != null) {
1253
- result = ctx.And(result, vars[idx].le(bound));
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
- return result;
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 encodeInjectionFire(ctx, idx, mVars, mPrimeVars, P) {
1259
- let fire = ctx.Bool.val(true);
1260
- for (let i = 0; i < P; i++) {
1261
- if (i === idx) {
1262
- fire = ctx.And(fire, mPrimeVars[i].eq(mVars[i].add(1)));
1263
- } else {
1264
- fire = ctx.And(fire, mPrimeVars[i].eq(mVars[i]));
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 encodeInjectionGuard(ctx, idx, bound, mVars) {
1270
- return bound === null ? ctx.Bool.val(true) : mVars[idx].lt(bound);
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
- function encodeStepRelation(ctx, flatNet, mVars, mPrimeVars) {
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 disjuncts = [];
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
- const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
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 [name, bound] of flatNet.environmentInjection) {
1283
- const idx = flatNet.placeIndex.get(name);
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
- if (disjuncts.length === 0) return ctx.Bool.val(false);
1291
- return ctx.Or(...disjuncts);
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 injectedEnvIndices(flatNet) {
1294
- const out = /* @__PURE__ */ new Map();
1420
+ function resolveEnvInjection(flatNet) {
1421
+ const out = [];
1295
1422
  for (const [name, bound] of flatNet.environmentInjection) {
1296
- const idx = flatNet.placeIndex.get(name);
1297
- if (idx != null) out.set(idx, bound);
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 encodeEnabled(ctx, ft, flatNet, mVars, P, relaxEnv = false) {
1302
- let result = ctx.Bool.val(true);
1303
- const envInj = relaxEnv ? injectedEnvIndices(flatNet) : void 0;
1304
- for (let p = 0; p < P; p++) {
1305
- const pre = ft.preVector[p];
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
- return result;
1435
+ out.sort((a, b) => a[0] - b[0]);
1436
+ return out;
1329
1437
  }
1330
- function encodeFire(ctx, ft, _flatNet, mVars, mPrimeVars, P) {
1331
- let result = ctx.Bool.val(true);
1332
- for (let p = 0; p < P; p++) {
1333
- const isReset = ft.resetPlaces.includes(p);
1334
- if (isReset || ft.consumeAll[p]) {
1335
- result = ctx.And(result, mPrimeVars[p].eq(ft.postVector[p]));
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[p] - ft.preVector[p];
1338
- if (delta === 0) {
1339
- result = ctx.And(result, mPrimeVars[p].eq(mVars[p]));
1340
- } else {
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
- return result;
1467
+ for (let i = 0; i < P; i++) conditions.push(`(>= ${mpVars[i]} 0)`);
1468
+ return conditions;
1346
1469
  }
1347
- function encodeErrorRule(ctx, fp, reachable, error, flatNet, property, sinkPlaces, P) {
1348
- const Int = ctx.Int;
1349
- const mVars = [];
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
- mVars.push(Int.const(`em${i}`));
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
- const reachBody = reachable.call(...mVars);
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(ctx, flatNet, property, sinkPlaces, mVars, P) {
1546
+ function encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject) {
1362
1547
  switch (property.type) {
1363
- case "deadlock-free": {
1364
- const deadlock = encodeDeadlock(ctx, flatNet, mVars, P);
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 idx1 = flatNetIndexOf(flatNet, property.p1);
1379
- const idx2 = flatNetIndexOf(flatNet, property.p2);
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 idx = flatNetIndexOf(flatNet, property.place);
1391
- if (idx < 0) throw new Error(`BranchPlaceBound references unknown place: ${property.place.name}`);
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
- let allMarked = ctx.Bool.val(true);
1402
- for (const place of property.places) {
1403
- const idx = flatNetIndexOf(flatNet, place);
1404
- if (idx >= 0) {
1405
- allMarked = ctx.And(allMarked, mVars[idx].ge(1));
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(ctx, flatNet, mVars, P) {
1413
- let deadlock = ctx.Bool.val(true);
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 enabled = encodeEnabled(
1416
- ctx,
1417
- ft,
1418
- flatNet,
1419
- mVars,
1420
- P,
1421
- /* relaxEnv */
1422
- true
1423
- );
1424
- deadlock = ctx.And(deadlock, ctx.Not(enabled));
1425
- }
1426
- return deadlock;
1427
- }
1428
- function encodeInvariantConstraints(ctx, invariants, mVars, P) {
1429
- let result = ctx.Bool.val(true);
1430
- for (const inv of invariants) {
1431
- let sum = ctx.Int.val(0);
1432
- for (const idx of inv.support) {
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
- result = ctx.And(result, sum.eq(inv.constant));
1600
+ if (disableReasons.length === 0) return "false";
1601
+ disabledConditions.push(`(or ${disableReasons.join(" ")})`);
1438
1602
  }
1439
- return result;
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
- async function checkCertificate(ctx, answer, flatNet, initialMarking, property, invariants, sinkPlaces, timeoutMs) {
1444
- try {
1445
- if (answer == null) {
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: `certificate check error: ${e?.message ?? e}`,
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
- async function validateCandidate(ctx, candidate, flatNet, initialMarking, property, sinkPlaces, timeoutMs, mVars, mPrimeVars) {
1493
- const P = flatNet.places.length;
1494
- const Int = ctx.Int;
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
- return null;
1529
- }
1530
- function topLevelConjuncts(ctx, expr) {
1531
- if (!ctx.isQuantifier(expr) && ctx.isApp(expr) && String(expr.decl().name()) === "and") {
1532
- const out = [];
1533
- const n = expr.numArgs();
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
- if (decl === "=>" && b.numArgs() === 2 && isReachableApp(ctx, b.arg(0), P)) {
1579
- return { app: b.arg(0), phi: b.arg(1) };
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 null;
1642
+ return { type: "passed", invariant: certificate };
1582
1643
  }
1583
- function isReachableApp(ctx, expr, P) {
1584
- return ctx.isApp(expr) && String(expr.decl().name()) === "Reachable" && expr.numArgs() === P;
1644
+ function vcScript(certificate, flatNet, initialMarking, property, sinkPlaces, invariants) {
1645
+ return script(buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants));
1585
1646
  }
1586
- async function checkUnsat(ctx, flatNet, mVars, timeoutMs, vc, assertions) {
1587
- const solver = new ctx.Solver();
1588
- if (timeoutMs > 0) {
1589
- solver.set("timeout", Math.min(timeoutMs, 2147483647));
1590
- }
1591
- for (const assertion of assertions) {
1592
- solver.add(assertion);
1593
- }
1594
- const status = await solver.check();
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
- let why = null;
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
- why = String(solver.reasonUnknown());
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
- return { vc, detail: `solver returned UNKNOWN${why == null || why === "" ? "" : ` (${why})`}` };
1745
+ const r = reasonUnknown(reply);
1746
+ return r == null ? "solver returned UNKNOWN" : `solver returned UNKNOWN (${r})`;
1609
1747
  }
1610
- function describeWitness(solver, flatNet, mVars) {
1611
- try {
1612
- const model = solver.model();
1613
- const parts = [];
1614
- let length = 0;
1615
- for (let i = 0; i < mVars.length; i++) {
1616
- const value = model.eval(mVars[i], false);
1617
- const text = value == null ? "" : String(value);
1618
- if (!/^(-?\d+|\(- \d+\))$/.test(text)) continue;
1619
- const part = `${flatNet.places[i].name}=${text}`;
1620
- parts.push(part);
1621
- length += part.length + 2;
1622
- if (length > 160) {
1623
- parts.push("...");
1624
- break;
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
- return parts.length === 0 ? null : parts.join(", ");
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 describeDecodeFailure(failure) {
2167
- if (failure == null) {
2168
- return "no Reachable applications with concrete arguments found in the derivation";
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 extractTrace(ctx, expr, flatNet, trace, transitions, stateByKey, counters) {
2199
- if (expr == null) return;
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
- if (name === "Reachable") {
2210
- const numArgs = expr.numArgs();
2211
- if (numArgs === P) {
2212
- const marking = extractMarking(ctx, expr, flatNet);
2213
- if (marking != null) {
2214
- trace.push(marking);
2215
- const key = marking.toString();
2216
- if (!stateByKey.has(key)) stateByKey.set(key, marking);
2217
- } else {
2218
- counters.skipped++;
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
- try {
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 extractMarking(ctx, reachableApp, flatNet) {
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 < P; i++) {
2239
- const arg = reachableApp.arg(i);
2240
- if (ctx.isIntVal(arg)) {
2241
- const tokens = Number(arg.value());
2242
- if (tokens > 0) {
2243
- builder.tokens(flatNet.places[i], tokens);
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
- return null;
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 builder.build();
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) && inv.constant >= 1 && coloured.every((pid) => w(inv, pid) >= 1)) {
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
- covered[i] = true;
2503
- touches = true;
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
- if (touches) sumConst += inv.constant;
2661
+ sumConst += inv.constant;
2507
2662
  }
2508
- if (covered.every((c) => c) && sumConst >= 1) return sumConst;
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(ctx, plan, P) {
2729
+ function buildLayout(plan, P) {
2574
2730
  const colUnc = new Array(P).fill(-1);
2575
2731
  const colCol = Array.from({ length: P }, () => []);
2576
- let nCols = 0;
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++) idxs.push(nCols++);
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] = nCols++;
2744
+ colUnc[i] = cur.length;
2745
+ cur.push(`m${i}`);
2746
+ nxt.push(`m${i}p`);
2584
2747
  }
2585
2748
  }
2586
- const cur = [];
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 encodeColoured(ctx, fp, plan, flat, initial, property, invariants, sinkPlaces = /* @__PURE__ */ new Set()) {
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(ctx, plan, P);
2598
- const intSort = ctx.Int.sort();
2599
- const boolSort = ctx.Bool.sort();
2600
- const markingSorts = new Array(lay.nCols).fill(intSort);
2601
- const reachable = ctx.Function.declare("Reachable", ...markingSorts, boolSort);
2602
- fp.registerRelation(reachable);
2603
- const error = ctx.Function.declare("Error", boolSort);
2604
- fp.registerRelation(error);
2605
- const initArgs = new Array(lay.nCols);
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++) initArgs[lay.colCol[i][c]] = ctx.Int.val(0);
2768
+ for (let c = 0; c < k; c++) init.push("0");
2609
2769
  } else {
2610
- initArgs[lay.colUnc[i]] = ctx.Int.val(initial.tokens(flat.places[i]));
2770
+ init.push(String(initial.tokens(flat.places[i])));
2611
2771
  }
2612
2772
  }
2613
- fp.addRule(reachable.call(...initArgs), "init");
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
- if (cls.kind === "untouched") {
2618
- addRule(
2619
- ctx,
2620
- fp,
2621
- reachable,
2622
- lay,
2623
- plan,
2624
- invariants,
2625
- `${ft.name}_u`,
2626
- (enab, upd) => uncolouredIncidence(ctx, lay, plan, ft, enab, upd)
2627
- );
2628
- } else if (cls.kind === "mint") {
2629
- const colouredOut = cls.colouredOut;
2630
- for (let c = 0; c < k; c++) {
2631
- const cc = c;
2632
- addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_mint_${cc}`, (enab, upd) => {
2633
- uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
2634
- for (const q of plan.coloured) enab.push(lay.cur[lay.colCol[q][cc]].eq(0));
2635
- for (const o of colouredOut) {
2636
- const col = lay.colCol[o][cc];
2637
- upd.set(col, lay.cur[col].add(1));
2638
- }
2639
- });
2640
- }
2641
- } else if (cls.kind === "join") {
2642
- const colouredIn = cls.colouredIn;
2643
- for (let c = 0; c < k; c++) {
2644
- const cc = c;
2645
- addRule(ctx, fp, reachable, lay, plan, invariants, `${ft.name}_join_${cc}`, (enab, upd) => {
2646
- uncolouredIncidence(ctx, lay, plan, ft, enab, upd);
2647
- for (const ip of colouredIn) {
2648
- const col = lay.colCol[ip][cc];
2649
- enab.push(lay.cur[col].ge(1));
2650
- upd.set(col, lay.cur[col].add(-1));
2651
- }
2652
- });
2653
- }
2654
- } else {
2655
- const inputCol = cls.inputCol;
2656
- const colouredOut = cls.colouredOut;
2657
- for (let c = 0; c < k; c++) {
2658
- const cc = c;
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
- if (!addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property, sinkPlaces)) {
2673
- return null;
2674
- }
2675
- return {
2676
- errorExpr: error.call(),
2677
- reachableDecl: reachable
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 addRule(ctx, fp, reachable, lay, plan, invariants, ruleName, fill) {
2831
+ function encodeRule(plan, lay, invariants, fill) {
2681
2832
  const enab = [];
2682
- const upd = /* @__PURE__ */ new Map();
2833
+ const upd = [];
2683
2834
  fill(enab, upd);
2684
- const conds = [reachable.call(...lay.cur), ...enab];
2685
- for (let col = 0; col < lay.nCols; col++) {
2686
- const expr = upd.get(col);
2687
- if (expr !== void 0) {
2688
- conds.push(lay.nxt[col].eq(expr), lay.nxt[col].ge(0));
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
- conds.push(lay.nxt[col].eq(lay.cur[col]));
2844
+ conditions.push(`(= ${lay.nxt[col]} ${lay.cur[col]})`);
2691
2845
  }
2692
2846
  }
2693
2847
  for (const inv of invariants) {
2694
- const eq = liftedInvariant(ctx, inv, plan, lay, lay.nxt);
2695
- if (eq) conds.push(eq);
2848
+ const eq = liftedInvariant(inv, plan, lay, lay.nxt);
2849
+ if (eq != null) conditions.push(eq);
2696
2850
  }
2697
- const body = ctx.And(...conds);
2698
- const head = reachable.call(...lay.nxt);
2699
- const qRule = ctx.ForAll([...lay.cur, ...lay.nxt], ctx.Implies(body, head));
2700
- fp.addRule(qRule, ruleName);
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(ctx, lay, plan, ft, enab, upd) {
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].ge(pre));
2862
+ if (pre > 0) enab.push(`(>= ${lay.cur[col]} ${pre})`);
2709
2863
  if (ft.resetPlaces.includes(i) || ft.consumeAll[i]) {
2710
- upd.set(col, ctx.Int.val(ft.postVector[i]));
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 !== 0) upd.set(col, lay.cur[col].add(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]].eq(0));
2717
- for (const pid of ft.readPlaces) enab.push(lay.cur[lay.colUnc[pid]].ge(1));
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, vars) {
2874
+ function aggregate(plan, lay, place, names) {
2720
2875
  if (plan.isColoured[place]) {
2721
2876
  const cols = lay.colCol[place];
2722
- let sum = vars[cols[0]];
2723
- for (let c = 1; c < cols.length; c++) sum = sum.add(vars[cols[c]]);
2724
- return sum;
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 vars[lay.colUnc[place]];
2881
+ return names[lay.colUnc[place]];
2727
2882
  }
2728
- function liftedInvariant(ctx, inv, plan, lay, vars) {
2729
- if (inv.support.size === 0) return null;
2730
- let sum = ctx.Int.val(0);
2731
- for (const i of inv.support) {
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
- sum = sum.add(w === 1 ? agg : agg.mul(w));
2735
- }
2736
- return sum.eq(inv.constant);
2737
- }
2738
- function addErrorRule(ctx, fp, reachable, error, lay, plan, flat, property, sinkPlaces) {
2739
- const violation = encodeViolation(ctx, plan, lay, flat, property, lay.cur, sinkPlaces);
2740
- if (violation === null) return false;
2741
- const reachBody = reachable.call(...lay.cur);
2742
- const body = ctx.And(reachBody, violation);
2743
- const head = error.call();
2744
- const qRule = ctx.ForAll([...lay.cur], ctx.Implies(body, head));
2745
- fp.addRule(qRule, "error");
2746
- return true;
2747
- }
2748
- function encodeViolation(ctx, plan, lay, flat, property, cur, sinkPlaces) {
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 idx = flatNetIndexOf(flat, property.place);
2753
- if (idx < 0) return null;
2754
- return aggregate(plan, lay, idx, cur).gt(property.bound);
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(ctx, plan, lay, flat, sinkPlaces);
2918
+ return encodeColouredDeadlock(plan, lay, flat, sinkPlaces, envInj);
2773
2919
  case "joined-or-dead-lettered": {
2774
- const idx = flatNetIndexOf(flat, property.pending);
2775
- if (idx < 0) return null;
2776
- const deadlock = encodeColouredDeadlock(ctx, plan, lay, flat, sinkPlaces);
2777
- return ctx.And(deadlock, aggregate(plan, lay, idx, cur).ge(1));
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 andAll(ctx, xs) {
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 !== null && ft.preVector[i] > bound) permanentlyDisabled = true;
2934
+ if (bound != null && ft.preVector[i] > bound) permanentlyDisabled = true;
2810
2935
  continue;
2811
2936
  }
2812
- reasons.push(lay.cur[lay.colUnc[i]].lt(ft.preVector[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 !== null && bound < 1) permanentlyDisabled = true;
2943
+ if (bound != null && bound < 1) permanentlyDisabled = true;
2821
2944
  continue;
2822
2945
  }
2823
- reasons.push(lay.cur[lay.colUnc[rd]].lt(1));
2946
+ reasons.push(`(< ${lay.cur[lay.colUnc[rd]]} 1)`);
2824
2947
  }
2825
- return { reasons, permanentlyDisabled };
2948
+ return permanentlyDisabled;
2826
2949
  }
2827
- function colouredDisabledTerm(ctx, cls, plan, lay) {
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]].ge(1));
2836
- perColour.push(orAll(ctx, present));
2961
+ const present = plan.coloured.map((q) => `(>= ${lay.cur[lay.colCol[q][c]]} 1)`);
2962
+ perColour.push(`(or ${present.join(" ")})`);
2837
2963
  }
2838
- return andAll(ctx, perColour);
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]].eq(0));
2844
- perColour.push(orAll(ctx, missing));
2969
+ const missing = cls.colouredIn.map((i) => `(= ${lay.cur[lay.colCol[i][c]]} 0)`);
2970
+ perColour.push(`(or ${missing.join(" ")})`);
2845
2971
  }
2846
- return andAll(ctx, perColour);
2972
+ return `(and ${perColour.join(" ")})`;
2847
2973
  }
2848
2974
  case "consume": {
2849
2975
  const perColour = [];
2850
- for (let c = 0; c < k; c++) {
2851
- perColour.push(lay.cur[lay.colCol[cls.inputCol][c]].eq(0));
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(ctx, plan, lay, flat, sinkPlaces) {
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 { reasons, permanentlyDisabled } = uncolouredDisable(ft, lay, plan, envInj);
2986
+ const reasons = [];
2987
+ const permanentlyDisabled = uncolouredDisable(ft, lay, plan, envInj, reasons);
2864
2988
  if (permanentlyDisabled) {
2865
- disabledConditions.push(ctx.Bool.val(true));
2989
+ disabledConditions.push("true");
2866
2990
  continue;
2867
2991
  }
2868
- const term = colouredDisabledTerm(ctx, cls, plan, lay);
2869
- if (term !== null) reasons.push(term);
2870
- if (reasons.length === 0) {
2871
- return ctx.Bool.val(false);
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
- if (sinkIndices.size > 0) {
2881
- const nonSink = [];
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 andAll(ctx, disabledConditions);
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
- constructor(base, names, colouredOrder) {
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.key = `${base.marking.toString()}|${base.firingDomain.toString()}||${names.canonicalKey(colouredOrder)}`;
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 initial = new NameStateClass(base0, new NameMarking(), fragment.colouredOrder);
3222
+ const baseIntern = /* @__PURE__ */ new Map();
3223
+ const nameIntern = /* @__PURE__ */ new Map();
3105
3224
  const indexOf = /* @__PURE__ */ new Map();
3106
- graph.pushClass(initial, indexOf);
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 succ = new NameStateClass(baseSucc, nm, fragment.colouredOrder);
3137
- let toIdx = indexOf.get(succ.key);
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(succ, indexOf);
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(c.key, idx);
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
- outcome.verdict,
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: invariants, dropped: droppedInvariants } = validateInvariantsExact(
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: ${invariants.length} P-invariant(s)`);
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 colouredPlan = hasMatch && nuBounded ? buildColouredPlan(
3666
- this.net,
3667
- flatNet,
3668
- this._initialMarking,
3669
- this._budgetPlaces,
3670
- this._fragmentMode,
3671
- this._carrierPlaces,
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
- runner = await createSpacerRunner(this._timeoutMs);
3962
+ solver = resolveZ3();
3677
3963
  } catch (e) {
3678
- report.push(` ERROR: ${e.message ?? e}
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: Z3 initialization error: ${e.message ?? e}`);
3682
- return buildResult(
3683
- { type: "unknown", reason: `Z3 init error: ${e.message ?? e}` },
3684
- report.join("\n"),
3685
- invariants,
3686
- [],
3687
- [],
3688
- [],
3689
- performance.now() - start,
3690
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
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
- try {
3694
- let encoding;
3695
- if (colouredPlan != null) {
3696
- report.push(
3697
- ` \u03BD-encoding: name-coloured (exact within budget k=${colouredPlan.k}; ${colouredPlan.coloured.length} coloured place(s))`
3698
- );
3699
- encoding = encodeColoured(runner.ctx, runner.fp, colouredPlan, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
3700
- if (encoding == null) {
3701
- const reason = "property names a place that does not resolve in the net; refusing to certify (the encoding would be vacuously proven)";
3702
- report.push(" Status: UNKNOWN (unresolved property place)\n");
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
- { type: "unknown", reason },
3707
- report.join("\n"),
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
- } else {
3717
- encoding = encode(runner.ctx, runner.fp, flatNet, this._initialMarking, this._property, invariants, this._sinkPlaces);
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
- const queryResult = await runner.query(encoding.errorExpr, encoding.reachableDecl);
3720
- switch (queryResult.type) {
3721
- case "proven": {
3722
- if (this._environmentPlaces.size > 0 && this._environmentMode.type === "ignore") {
3723
- const reason = "environment places present but not modeled (mode=ignore); a proof would be vacuous \u2014 use alwaysAvailable() or bounded(k) to model external injection";
3724
- report.push(` Status: UNSAT, but vacuous under ignore mode
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
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
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
- case "unknown": {
3909
- report.push(` Status: UNKNOWN (${queryResult.reason})
3910
- `);
3911
- report.push("=== RESULT ===\n");
3912
- report.push(`UNKNOWN: Could not determine ${propDesc}`);
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
- } catch (e) {
3927
- report.push(` ERROR: ${e.message ?? e}
4141
+ case "unknown": {
4142
+ report.push(` Status: UNKNOWN (${queryResult.reason})
3928
4143
  `);
3929
- report.push("=== RESULT ===\n");
3930
- report.push(`UNKNOWN: Z3 solver error: ${e.message ?? e}`);
3931
- return buildResult(
3932
- { type: "unknown", reason: `Z3 error: ${e.message ?? e}` },
3933
- report.join("\n"),
3934
- invariants,
3935
- [],
3936
- [],
3937
- [],
3938
- performance.now() - start,
3939
- { places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
3940
- );
3941
- } finally {
3942
- runner.dispose();
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 substituteNames(formula, flatNet) {
4060
- for (let i = flatNet.places.length - 1; i >= 0; i--) {
4061
- formula = formula.replace(new RegExp(`\\bm${i}\\b`, "g"), flatNet.places[i].name);
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 formula;
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
- createSpacerRunner,
4137
- flatNetPlaceCount,
4138
- flatNetTransitionCount,
4139
- flatNetIndexOf,
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-JZIEWVAV.js.map
4406
+ //# sourceMappingURL=chunk-FD3S4TZM.js.map