libpetri 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4HMFNYTH.js → chunk-BPGE7GZR.js} +2 -2
- package/dist/{chunk-75KEJQGC.js → chunk-EL4E6LVO.js} +1002 -205
- package/dist/chunk-EL4E6LVO.js.map +1 -0
- package/dist/debug/index.d.ts +2 -2
- package/dist/doclet/index.d.ts +1 -1
- package/dist/doclet/resources/petrinet-diagrams.js +4892 -4896
- package/dist/{event-store-hlH3-bzJ.d.ts → event-store-CUyHiIH7.d.ts} +1 -1
- package/dist/export/index.d.ts +1 -1
- package/dist/index.d.ts +23 -4
- package/dist/index.js +69 -4
- package/dist/index.js.map +1 -1
- package/dist/{petri-net-CH7UvjOW.d.ts → petri-net-6G5r1Wwb.d.ts} +59 -2
- package/dist/render-dom/index.js +1 -1
- package/dist/verification/index.d.ts +435 -14
- package/dist/verification/index.js +31 -1
- package/dist/verification/index.js.map +1 -1
- package/dist/viewer/index.js +1 -1
- package/dist/viewer/viewer.iife.js +4892 -4896
- package/package.json +5 -5
- package/dist/chunk-75KEJQGC.js.map +0 -1
- /package/dist/{chunk-4HMFNYTH.js.map → chunk-BPGE7GZR.js.map} +0 -0
|
@@ -220,6 +220,11 @@ function withTimeout(action, timeoutMs, timeoutPlace2, timeoutValue) {
|
|
|
220
220
|
};
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
// src/verification/programming-error.ts
|
|
224
|
+
function rethrowIfProgrammingError(e) {
|
|
225
|
+
if (e instanceof TypeError || e instanceof ReferenceError) throw e;
|
|
226
|
+
}
|
|
227
|
+
|
|
223
228
|
// src/verification/marking-state.ts
|
|
224
229
|
var MARKING_STATE_KEY = /* @__PURE__ */ Symbol("MarkingState.internal");
|
|
225
230
|
var MarkingState = class _MarkingState {
|
|
@@ -252,9 +257,9 @@ var MarkingState = class _MarkingState {
|
|
|
252
257
|
}
|
|
253
258
|
/** Returns the total number of tokens. */
|
|
254
259
|
totalTokens() {
|
|
255
|
-
let
|
|
256
|
-
for (const count of this.tokenCounts.values())
|
|
257
|
-
return
|
|
260
|
+
let sum2 = 0;
|
|
261
|
+
for (const count of this.tokenCounts.values()) sum2 += count;
|
|
262
|
+
return sum2;
|
|
258
263
|
}
|
|
259
264
|
/** Checks if no tokens exist anywhere. */
|
|
260
265
|
isEmpty() {
|
|
@@ -368,6 +373,53 @@ function propertyDescription(prop) {
|
|
|
368
373
|
}
|
|
369
374
|
}
|
|
370
375
|
|
|
376
|
+
// src/verification/rest-set.ts
|
|
377
|
+
function strandingExcuses(flatNet, sinkPlaces, conditional) {
|
|
378
|
+
const P = flatNet.places.length;
|
|
379
|
+
const excuses = new Array(P);
|
|
380
|
+
for (let pid = 0; pid < P; pid++) excuses[pid] = [];
|
|
381
|
+
for (const sink of sinkPlaces) {
|
|
382
|
+
const pid = flatNet.placeIndex.get(sink.name);
|
|
383
|
+
if (pid != null) excuses[pid] = null;
|
|
384
|
+
}
|
|
385
|
+
for (const { marker, places } of conditional) {
|
|
386
|
+
const mid = flatNet.placeIndex.get(marker.name);
|
|
387
|
+
if (mid == null) continue;
|
|
388
|
+
excuses[mid] = null;
|
|
389
|
+
for (const place of places) {
|
|
390
|
+
const pid = flatNet.placeIndex.get(place.name);
|
|
391
|
+
if (pid == null) continue;
|
|
392
|
+
const list = excuses[pid];
|
|
393
|
+
if (list != null && !list.includes(mid)) list.push(mid);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
for (const list of excuses) if (list != null) list.sort((a, b) => a - b);
|
|
397
|
+
return excuses;
|
|
398
|
+
}
|
|
399
|
+
function strandsToken(m, sinkPlaces, conditional) {
|
|
400
|
+
const resting = /* @__PURE__ */ new Set();
|
|
401
|
+
for (const s of sinkPlaces) resting.add(s.name);
|
|
402
|
+
for (const { marker, places } of conditional) {
|
|
403
|
+
resting.add(marker.name);
|
|
404
|
+
if (m.hasTokens(marker)) {
|
|
405
|
+
for (const p of places) resting.add(p.name);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
for (const p of m.placesWithTokens()) {
|
|
409
|
+
if (!resting.has(p.name)) return true;
|
|
410
|
+
}
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
function describeSinks(sinkPlaces, conditional) {
|
|
414
|
+
const parts = [];
|
|
415
|
+
if (sinkPlaces.size > 0) parts.push(`sinks: ${[...sinkPlaces].map((p) => p.name).join(", ")}`);
|
|
416
|
+
for (const { marker, places } of conditional) {
|
|
417
|
+
const names = [...places].map((p) => p.name);
|
|
418
|
+
parts.push(names.length === 0 ? `when ${marker.name}` : `when ${marker.name}: ${names.join(", ")}`);
|
|
419
|
+
}
|
|
420
|
+
return parts.length === 0 ? null : parts.join("; ");
|
|
421
|
+
}
|
|
422
|
+
|
|
371
423
|
// src/verification/encoding/flat-transition.ts
|
|
372
424
|
function flatTransition(name, source, branchIndex, preVector, postVector, inhibitorPlaces, readPlaces, resetPlaces, consumeAll) {
|
|
373
425
|
return {
|
|
@@ -826,6 +878,8 @@ function strengthenWithSemiflows(invariants, semiflows) {
|
|
|
826
878
|
}
|
|
827
879
|
return { invariants: strengthened, added };
|
|
828
880
|
}
|
|
881
|
+
var MAX_SEMIFLOW_ROWS = 8192;
|
|
882
|
+
var MAX_SEMIFLOW_CANDIDATES = 65536;
|
|
829
883
|
function computePSemiflows(matrix, flatNet, initialMarking) {
|
|
830
884
|
const np = matrix.numPlaces();
|
|
831
885
|
const nt = matrix.numTransitions();
|
|
@@ -843,19 +897,21 @@ function computePSemiflows(matrix, flatNet, initialMarking) {
|
|
|
843
897
|
const next = rows.filter((r) => r.sig[t] === 0);
|
|
844
898
|
const pos = rows.filter((r) => r.sig[t] > 0);
|
|
845
899
|
const neg = rows.filter((r) => r.sig[t] < 0);
|
|
846
|
-
|
|
847
|
-
for (const
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
900
|
+
outer:
|
|
901
|
+
for (const rp of pos) {
|
|
902
|
+
for (const rn of neg) {
|
|
903
|
+
if (next.length >= MAX_SEMIFLOW_CANDIDATES) break outer;
|
|
904
|
+
const cp = -rn.sig[t];
|
|
905
|
+
const cn = rp.sig[t];
|
|
906
|
+
const sig = combineRow(cp, rp.sig, cn, rn.sig);
|
|
907
|
+
const weight = combineRow(cp, rp.weight, cn, rn.weight);
|
|
908
|
+
if (sig === null || weight === null) continue;
|
|
909
|
+
reduceGcd(sig, weight);
|
|
910
|
+
next.push({ sig, weight });
|
|
911
|
+
}
|
|
855
912
|
}
|
|
856
|
-
}
|
|
857
913
|
rows = keepSupportMinimal(next);
|
|
858
|
-
if (rows.length >
|
|
914
|
+
if (rows.length > MAX_SEMIFLOW_ROWS) rows.length = MAX_SEMIFLOW_ROWS;
|
|
859
915
|
}
|
|
860
916
|
const semiflows = [];
|
|
861
917
|
for (const { weight } of rows) {
|
|
@@ -892,17 +948,43 @@ function reduceGcd(sig, weight) {
|
|
|
892
948
|
}
|
|
893
949
|
}
|
|
894
950
|
function keepSupportMinimal(rows) {
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
for (let
|
|
904
|
-
if (
|
|
905
|
-
|
|
951
|
+
const n = rows.length;
|
|
952
|
+
if (n < 2) return rows;
|
|
953
|
+
const words = rows[0].weight.length + 31 >>> 5 || 1;
|
|
954
|
+
const bits = new Uint32Array(n * words);
|
|
955
|
+
const sizes = new Int32Array(n);
|
|
956
|
+
for (let i = 0; i < n; i++) {
|
|
957
|
+
const w = rows[i].weight;
|
|
958
|
+
let size = 0;
|
|
959
|
+
for (let p = 0; p < w.length; p++) {
|
|
960
|
+
if (w[p] !== 0) {
|
|
961
|
+
const idx = i * words + (p >>> 5);
|
|
962
|
+
bits[idx] = bits[idx] | 1 << (p & 31);
|
|
963
|
+
size++;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
sizes[i] = size;
|
|
967
|
+
}
|
|
968
|
+
const order = new Int32Array(n);
|
|
969
|
+
for (let i = 0; i < n; i++) order[i] = i;
|
|
970
|
+
order.sort((a, b) => sizes[a] - sizes[b]);
|
|
971
|
+
const keep = new Array(n).fill(true);
|
|
972
|
+
for (let oi = 0; oi < n; oi++) {
|
|
973
|
+
const i = order[oi];
|
|
974
|
+
const base = i * words;
|
|
975
|
+
for (let oj = 0; oj < oi; oj++) {
|
|
976
|
+
const j = order[oj];
|
|
977
|
+
if (sizes[j] >= sizes[i]) break;
|
|
978
|
+
const jbase = j * words;
|
|
979
|
+
let subset = true;
|
|
980
|
+
for (let w = 0; w < words; w++) {
|
|
981
|
+
const jb = bits[jbase + w];
|
|
982
|
+
if ((jb & ~bits[base + w]) !== 0) {
|
|
983
|
+
subset = false;
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (subset) {
|
|
906
988
|
keep[i] = false;
|
|
907
989
|
break;
|
|
908
990
|
}
|
|
@@ -1241,7 +1323,8 @@ function locateZ3(program, env = process.env) {
|
|
|
1241
1323
|
const isFile = (p) => {
|
|
1242
1324
|
try {
|
|
1243
1325
|
return existsSync(p) && statSync(p).isFile();
|
|
1244
|
-
} catch {
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
rethrowIfProgrammingError(e);
|
|
1245
1328
|
return false;
|
|
1246
1329
|
}
|
|
1247
1330
|
};
|
|
@@ -1252,9 +1335,9 @@ function locateZ3(program, env = process.env) {
|
|
|
1252
1335
|
const windows = process.platform === "win32";
|
|
1253
1336
|
for (const dir of searchPath.split(path.delimiter)) {
|
|
1254
1337
|
if (dir === "") continue;
|
|
1255
|
-
const
|
|
1256
|
-
if (isFile(
|
|
1257
|
-
if (windows && isFile(
|
|
1338
|
+
const candidate = path.join(dir, program);
|
|
1339
|
+
if (isFile(candidate)) return candidate;
|
|
1340
|
+
if (windows && isFile(candidate + ".exe")) return candidate + ".exe";
|
|
1258
1341
|
}
|
|
1259
1342
|
return null;
|
|
1260
1343
|
}
|
|
@@ -1300,7 +1383,8 @@ function z3Available(env = process.env) {
|
|
|
1300
1383
|
try {
|
|
1301
1384
|
resolveZ3(env);
|
|
1302
1385
|
return true;
|
|
1303
|
-
} catch {
|
|
1386
|
+
} catch (e) {
|
|
1387
|
+
rethrowIfProgrammingError(e);
|
|
1304
1388
|
return false;
|
|
1305
1389
|
}
|
|
1306
1390
|
}
|
|
@@ -1373,6 +1457,7 @@ async function runZ3Spacer(solver, timeoutMs, smt2, phase) {
|
|
|
1373
1457
|
try {
|
|
1374
1458
|
reply = await runZ3Text(solver, smt2, phase, timeoutMs, ["fp.engine=spacer"]);
|
|
1375
1459
|
} catch (e) {
|
|
1460
|
+
rethrowIfProgrammingError(e);
|
|
1376
1461
|
return { type: "unknown", reason: String(e?.message ?? e) };
|
|
1377
1462
|
}
|
|
1378
1463
|
const stdout = reply.stdout.trim();
|
|
@@ -1391,36 +1476,50 @@ async function runZ3Spacer(solver, timeoutMs, smt2, phase) {
|
|
|
1391
1476
|
}
|
|
1392
1477
|
|
|
1393
1478
|
// src/verification/z3/smt-encoder.ts
|
|
1394
|
-
function encode(flatNet, initialMarking, property, invariants, sinkPlaces = /* @__PURE__ */ new Set(), produceProofs = false) {
|
|
1479
|
+
function encode(flatNet, initialMarking, property, invariants, sinkPlaces = /* @__PURE__ */ new Set(), produceProofs = false, conditionalSinks = []) {
|
|
1480
|
+
return encodeNet(flatNet, initialMarking, property, invariants, { sinkPlaces, produceProofs, conditionalSinks });
|
|
1481
|
+
}
|
|
1482
|
+
function encodeNet(flatNet, initialMarking, property, invariants, options = {}) {
|
|
1483
|
+
const sinkPlaces = options.sinkPlaces ?? /* @__PURE__ */ new Set();
|
|
1484
|
+
const produceProofs = options.produceProofs ?? false;
|
|
1485
|
+
const conditionalSinks = options.conditionalSinks ?? [];
|
|
1395
1486
|
const P = flatNet.places.length;
|
|
1487
|
+
const T = options.stateEquation ? flatNet.transitions.length : 0;
|
|
1396
1488
|
const lines = [];
|
|
1397
1489
|
const envInject = resolveEnvInjection(flatNet);
|
|
1398
1490
|
if (produceProofs) lines.push("(set-option :produce-proofs true)");
|
|
1399
1491
|
lines.push("(set-logic HORN)");
|
|
1400
1492
|
lines.push("");
|
|
1401
|
-
lines.push(`(declare-fun Reachable (${ints(P).join(" ")}) Bool)`);
|
|
1493
|
+
lines.push(`(declare-fun Reachable (${ints(P + T).join(" ")}) Bool)`);
|
|
1402
1494
|
lines.push("(declare-fun Error () Bool)");
|
|
1403
1495
|
lines.push("");
|
|
1404
1496
|
const mVars = vars(P, "");
|
|
1405
1497
|
const mpVars = vars(P, "p");
|
|
1498
|
+
const nVars = counterVars(T, "");
|
|
1499
|
+
const npVars = counterVars(T, "p");
|
|
1406
1500
|
const m0 = [];
|
|
1407
1501
|
for (let i = 0; i < P; i++) m0.push(String(initialMarking.tokens(flatNet.places[i])));
|
|
1502
|
+
for (let k = 0; k < T; k++) m0.push("0");
|
|
1408
1503
|
lines.push(`(assert (Reachable ${m0.join(" ")}))`);
|
|
1409
1504
|
lines.push("");
|
|
1410
|
-
|
|
1411
|
-
|
|
1505
|
+
const equation = T > 0 ? stateEquationConditions(flatNet, initialMarking, npVars) : [];
|
|
1506
|
+
for (let k = 0; k < flatNet.transitions.length; k++) {
|
|
1507
|
+
const ft = flatNet.transitions[k];
|
|
1508
|
+
const strengthening = [...invariantConditions(invariants, mpVars)];
|
|
1509
|
+
if (T > 0) strengthening.push(...counterConditions(k, nVars, npVars), ...equation);
|
|
1510
|
+
lines.push(encodeTransitionRule(flatNet, ft, mVars, mpVars, nVars, npVars, strengthening));
|
|
1412
1511
|
}
|
|
1413
1512
|
for (const inj of envInject) {
|
|
1414
|
-
lines.push(encodeInjectionRule(P, inj.pid, inj.bound, mVars, mpVars));
|
|
1513
|
+
lines.push(encodeInjectionRule(P, inj.pid, inj.bound, mVars, mpVars, nVars, npVars));
|
|
1415
1514
|
}
|
|
1416
1515
|
lines.push("");
|
|
1417
|
-
lines.push(encodeErrorRule(flatNet, property, mVars, sinkPlaces, envInject));
|
|
1516
|
+
lines.push(encodeErrorRule(flatNet, property, mVars, nVars, sinkPlaces, envInject, conditionalSinks));
|
|
1418
1517
|
lines.push("");
|
|
1419
1518
|
lines.push("(assert (not Error))");
|
|
1420
1519
|
lines.push("(check-sat)");
|
|
1421
1520
|
if (produceProofs) lines.push("(get-proof)");
|
|
1422
1521
|
lines.push("(get-model)");
|
|
1423
|
-
return { smt2: lines.join("\n"), placeCount: P };
|
|
1522
|
+
return { smt2: lines.join("\n"), placeCount: P, counterCount: T };
|
|
1424
1523
|
}
|
|
1425
1524
|
function resolveEnvInjection(flatNet) {
|
|
1426
1525
|
const out = [];
|
|
@@ -1448,9 +1547,44 @@ function vars(P, suffix) {
|
|
|
1448
1547
|
for (let i = 0; i < P; i++) out.push(`m${i}${suffix}`);
|
|
1449
1548
|
return out;
|
|
1450
1549
|
}
|
|
1550
|
+
function counterVars(T, suffix) {
|
|
1551
|
+
const out = [];
|
|
1552
|
+
for (let k = 0; k < T; k++) out.push(`n${k}${suffix}`);
|
|
1553
|
+
return out;
|
|
1554
|
+
}
|
|
1451
1555
|
function quantified(names) {
|
|
1452
1556
|
return names.map((v) => `(${v} Int)`).join(" ");
|
|
1453
1557
|
}
|
|
1558
|
+
function equationPlaces(flatNet) {
|
|
1559
|
+
const excluded = new Set(nonlinearPlaces(flatNet));
|
|
1560
|
+
for (const inj of resolveEnvInjection(flatNet)) excluded.add(inj.pid);
|
|
1561
|
+
const out = [];
|
|
1562
|
+
for (let p = 0; p < flatNet.places.length; p++) if (!excluded.has(p)) out.push(p);
|
|
1563
|
+
return out;
|
|
1564
|
+
}
|
|
1565
|
+
function counterConditions(fired, nVars, npVars) {
|
|
1566
|
+
const conditions = [];
|
|
1567
|
+
for (let k = 0; k < nVars.length; k++) {
|
|
1568
|
+
conditions.push(k === fired ? `(= ${npVars[k]} (+ ${nVars[k]} 1))` : `(= ${npVars[k]} ${nVars[k]})`);
|
|
1569
|
+
}
|
|
1570
|
+
for (let k = 0; k < npVars.length; k++) conditions.push(`(>= ${npVars[k]} 0)`);
|
|
1571
|
+
return conditions;
|
|
1572
|
+
}
|
|
1573
|
+
function stateEquationConditions(flatNet, initialMarking, nVars, mVars = vars(flatNet.places.length, "p")) {
|
|
1574
|
+
const conditions = [];
|
|
1575
|
+
for (const p of equationPlaces(flatNet)) {
|
|
1576
|
+
const terms = [];
|
|
1577
|
+
for (let t = 0; t < flatNet.transitions.length; t++) {
|
|
1578
|
+
const ft = flatNet.transitions[t];
|
|
1579
|
+
const c = ft.postVector[p] - ft.preVector[p];
|
|
1580
|
+
if (c === 0) continue;
|
|
1581
|
+
terms.push(c === 1 ? nVars[t] : c === -1 ? `(- ${nVars[t]})` : c > 0 ? `(* ${c} ${nVars[t]})` : `(* (- ${-c}) ${nVars[t]})`);
|
|
1582
|
+
}
|
|
1583
|
+
const m0 = initialMarking.tokens(flatNet.places[p]);
|
|
1584
|
+
conditions.push(terms.length === 0 ? `(= ${mVars[p]} ${m0})` : `(= ${mVars[p]} (+ ${m0} ${terms.join(" ")}))`);
|
|
1585
|
+
}
|
|
1586
|
+
return conditions;
|
|
1587
|
+
}
|
|
1454
1588
|
function firingConditions(flatNet, ft, mVars, mpVars) {
|
|
1455
1589
|
const P = flatNet.places.length;
|
|
1456
1590
|
const conditions = [];
|
|
@@ -1477,8 +1611,8 @@ function invariantConditions(invariants, names) {
|
|
|
1477
1611
|
for (const inv of invariants) {
|
|
1478
1612
|
const terms = [...inv.support].sort((a, b) => a - b).map((i) => `(* ${inv.weights[i]} ${names[i]})`);
|
|
1479
1613
|
if (terms.length === 0) continue;
|
|
1480
|
-
const
|
|
1481
|
-
conditions.push(`(= ${
|
|
1614
|
+
const sum2 = terms.length === 1 ? terms[0] : `(+ ${terms.join(" ")})`;
|
|
1615
|
+
conditions.push(`(= ${sum2} ${inv.constant})`);
|
|
1482
1616
|
}
|
|
1483
1617
|
return conditions;
|
|
1484
1618
|
}
|
|
@@ -1494,50 +1628,61 @@ function injectionConditions(P, pid, bound, mVars, mpVars) {
|
|
|
1494
1628
|
}
|
|
1495
1629
|
return conditions;
|
|
1496
1630
|
}
|
|
1497
|
-
function encodeTransitionRule(flatNet, ft, mVars, mpVars,
|
|
1498
|
-
const conditions = [`(Reachable ${mVars.join(" ")})`];
|
|
1631
|
+
function encodeTransitionRule(flatNet, ft, mVars, mpVars, nVars, npVars, strengthening) {
|
|
1632
|
+
const conditions = [`(Reachable ${[...mVars, ...nVars].join(" ")})`];
|
|
1499
1633
|
conditions.push(...firingConditions(flatNet, ft, mVars, mpVars));
|
|
1500
|
-
conditions.push(...
|
|
1634
|
+
conditions.push(...strengthening);
|
|
1501
1635
|
conditions.push(...envBoundConditions(flatNet, mpVars));
|
|
1502
1636
|
const body = `(and ${conditions.join("\n ")})`;
|
|
1503
|
-
|
|
1637
|
+
const quantifiedVars = quantified([...mVars, ...mpVars, ...nVars, ...npVars]);
|
|
1638
|
+
return `(assert (forall (${quantifiedVars})
|
|
1504
1639
|
(=> ${body}
|
|
1505
|
-
(Reachable ${mpVars.join(" ")}))))`;
|
|
1640
|
+
(Reachable ${[...mpVars, ...npVars].join(" ")}))))`;
|
|
1506
1641
|
}
|
|
1507
|
-
function encodeInjectionRule(P, pid, bound, mVars, mpVars) {
|
|
1508
|
-
const conditions = [`(Reachable ${mVars.join(" ")})`];
|
|
1642
|
+
function encodeInjectionRule(P, pid, bound, mVars, mpVars, nVars, npVars) {
|
|
1643
|
+
const conditions = [`(Reachable ${[...mVars, ...nVars].join(" ")})`];
|
|
1509
1644
|
conditions.push(...injectionConditions(P, pid, bound, mVars, mpVars));
|
|
1645
|
+
if (nVars.length > 0) conditions.push(...counterConditions(-1, nVars, npVars));
|
|
1510
1646
|
const body = `(and ${conditions.join("\n ")})`;
|
|
1511
|
-
|
|
1647
|
+
const quantifiedVars = quantified([...mVars, ...mpVars, ...nVars, ...npVars]);
|
|
1648
|
+
return `(assert (forall (${quantifiedVars})
|
|
1512
1649
|
(=> ${body}
|
|
1513
|
-
(Reachable ${mpVars.join(" ")}))))`;
|
|
1650
|
+
(Reachable ${[...mpVars, ...npVars].join(" ")}))))`;
|
|
1514
1651
|
}
|
|
1515
1652
|
function conjoin(conditions) {
|
|
1516
1653
|
if (conditions.length === 0) return "true";
|
|
1517
1654
|
if (conditions.length === 1) return conditions[0];
|
|
1518
1655
|
return `(and ${conditions.join(" ")})`;
|
|
1519
1656
|
}
|
|
1520
|
-
function encodeStepRelationSmt2(flatNet) {
|
|
1657
|
+
function encodeStepRelationSmt2(flatNet, stateEquation = false) {
|
|
1521
1658
|
const P = flatNet.places.length;
|
|
1659
|
+
const T = stateEquation ? flatNet.transitions.length : 0;
|
|
1522
1660
|
const mVars = vars(P, "");
|
|
1523
1661
|
const mpVars = vars(P, "p");
|
|
1662
|
+
const nVars = counterVars(T, "");
|
|
1663
|
+
const npVars = counterVars(T, "p");
|
|
1524
1664
|
const disjuncts = [];
|
|
1525
|
-
for (
|
|
1665
|
+
for (let k = 0; k < flatNet.transitions.length; k++) {
|
|
1666
|
+
const ft = flatNet.transitions[k];
|
|
1526
1667
|
const conditions = firingConditions(flatNet, ft, mVars, mpVars);
|
|
1668
|
+
if (T > 0) conditions.push(...counterConditions(k, nVars, npVars));
|
|
1527
1669
|
conditions.push(...envBoundConditions(flatNet, mpVars));
|
|
1528
1670
|
disjuncts.push(conjoin(conditions));
|
|
1529
1671
|
}
|
|
1530
1672
|
for (const inj of resolveEnvInjection(flatNet)) {
|
|
1531
|
-
|
|
1673
|
+
const conditions = injectionConditions(P, inj.pid, inj.bound, mVars, mpVars);
|
|
1674
|
+
if (T > 0) conditions.push(...counterConditions(-1, nVars, npVars));
|
|
1675
|
+
disjuncts.push(conjoin(conditions));
|
|
1532
1676
|
}
|
|
1533
1677
|
if (disjuncts.length === 0) return "false";
|
|
1534
1678
|
if (disjuncts.length === 1) return disjuncts[0];
|
|
1535
1679
|
return `(or ${disjuncts.join("\n ")})`;
|
|
1536
1680
|
}
|
|
1537
|
-
function encodeErrorRule(flatNet, property, mVars, sinkPlaces, envInject) {
|
|
1538
|
-
const violation = encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject);
|
|
1539
|
-
|
|
1540
|
-
(
|
|
1681
|
+
function encodeErrorRule(flatNet, property, mVars, nVars, sinkPlaces, envInject, conditionalSinks) {
|
|
1682
|
+
const violation = encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject, conditionalSinks);
|
|
1683
|
+
const state = [...mVars, ...nVars];
|
|
1684
|
+
return `(assert (forall (${quantified(state)})
|
|
1685
|
+
(=> (and (Reachable ${state.join(" ")}) ${violation})
|
|
1541
1686
|
Error)))`;
|
|
1542
1687
|
}
|
|
1543
1688
|
function indexOrdered(flatNet, places) {
|
|
@@ -1548,19 +1693,16 @@ function indexOrdered(flatNet, places) {
|
|
|
1548
1693
|
}
|
|
1549
1694
|
return [...idx].sort((a, b) => a - b);
|
|
1550
1695
|
}
|
|
1551
|
-
function encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject) {
|
|
1696
|
+
function encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject, conditionalSinks = []) {
|
|
1552
1697
|
switch (property.type) {
|
|
1553
1698
|
// DeadlockFree (VER-002): a quiescent marking that STRANDS a token — holds one
|
|
1554
|
-
// in a place
|
|
1555
|
-
// is therefore not a violation (AC4).
|
|
1699
|
+
// in a place where resting is not permitted. The empty marking strands nothing
|
|
1700
|
+
// and is therefore not a violation (AC4). A conditional sink (VER-014) is
|
|
1701
|
+
// stranded only while every marker that would excuse it is unmarked.
|
|
1556
1702
|
case "deadlock-free": {
|
|
1557
1703
|
const conditions = encodeQuiescent(flatNet, mVars, envInject);
|
|
1558
1704
|
if (conditions == null) return "false";
|
|
1559
|
-
const
|
|
1560
|
-
const stranded = [];
|
|
1561
|
-
for (let pid = 0; pid < flatNet.places.length; pid++) {
|
|
1562
|
-
if (!sinks.has(pid)) stranded.push(`(>= ${mVars[pid]} 1)`);
|
|
1563
|
-
}
|
|
1705
|
+
const stranded = strandedConditions(strandingExcuses(flatNet, sinkPlaces, conditionalSinks), mVars);
|
|
1564
1706
|
if (stranded.length === 0) return "false";
|
|
1565
1707
|
conditions.push(`(or ${stranded.join(" ")})`);
|
|
1566
1708
|
return joinConditions(conditions);
|
|
@@ -1601,6 +1743,19 @@ function encodePropertyViolation(flatNet, property, mVars, sinkPlaces, envInject
|
|
|
1601
1743
|
}
|
|
1602
1744
|
}
|
|
1603
1745
|
}
|
|
1746
|
+
function strandedConditions(excuses, counts) {
|
|
1747
|
+
const stranded = [];
|
|
1748
|
+
for (let pid = 0; pid < excuses.length; pid++) {
|
|
1749
|
+
const markers = excuses[pid];
|
|
1750
|
+
if (markers == null) continue;
|
|
1751
|
+
if (markers.length === 0) {
|
|
1752
|
+
stranded.push(`(>= ${counts[pid]} 1)`);
|
|
1753
|
+
} else {
|
|
1754
|
+
stranded.push(`(and (>= ${counts[pid]} 1) ${markers.map((k) => `(= ${counts[k]} 0)`).join(" ")})`);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
return stranded;
|
|
1758
|
+
}
|
|
1604
1759
|
function joinConditions(conditions) {
|
|
1605
1760
|
return conditions.length === 0 ? "true" : `(and ${conditions.join("\n ")})`;
|
|
1606
1761
|
}
|
|
@@ -1639,6 +1794,9 @@ function encodeQuiescent(flatNet, mVars, envInject) {
|
|
|
1639
1794
|
}
|
|
1640
1795
|
return disabledConditions;
|
|
1641
1796
|
}
|
|
1797
|
+
function quiescenceUnreachable(flatNet, envInject) {
|
|
1798
|
+
return encodeQuiescent(flatNet, vars(flatNet.places.length, ""), envInject) === null;
|
|
1799
|
+
}
|
|
1642
1800
|
function injectionMap(flatNet) {
|
|
1643
1801
|
const out = /* @__PURE__ */ new Map();
|
|
1644
1802
|
for (const inj of resolveEnvInjection(flatNet)) out.set(inj.pid, inj.bound);
|
|
@@ -1647,7 +1805,7 @@ function injectionMap(flatNet) {
|
|
|
1647
1805
|
|
|
1648
1806
|
// src/verification/z3/certificate-checker.ts
|
|
1649
1807
|
var VC_LABELS = ["initiation (VC1)", "consecution (VC2)", "safety (VC3)"];
|
|
1650
|
-
async function checkCertificate(certificate, flatNet, initialMarking, property, invariants, sinkPlaces, solver, timeoutMs) {
|
|
1808
|
+
async function checkCertificate(certificate, flatNet, initialMarking, property, invariants, sinkPlaces, solver, timeoutMs, conditionalSinks = [], stateEquation = false) {
|
|
1651
1809
|
if (certificate == null) {
|
|
1652
1810
|
return {
|
|
1653
1811
|
type: "unavailable",
|
|
@@ -1660,11 +1818,12 @@ async function checkCertificate(certificate, flatNet, initialMarking, property,
|
|
|
1660
1818
|
if (!certificate.includes("(define-fun Reachable ") && !certificate.includes("(define-fun |Reachable| ")) {
|
|
1661
1819
|
return { type: "unavailable", reason: "certificate does not define Reachable", invariant: certificate };
|
|
1662
1820
|
}
|
|
1663
|
-
const vcs = buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants);
|
|
1821
|
+
const vcs = buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants, conditionalSinks, stateEquation);
|
|
1664
1822
|
let results;
|
|
1665
1823
|
try {
|
|
1666
1824
|
results = await runVcScript(script(vcs), timeoutMs, solver);
|
|
1667
1825
|
} catch (e) {
|
|
1826
|
+
rethrowIfProgrammingError(e);
|
|
1668
1827
|
return { type: "unavailable", reason: String(e?.message ?? e), invariant: certificate };
|
|
1669
1828
|
}
|
|
1670
1829
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -1675,8 +1834,8 @@ async function checkCertificate(certificate, flatNet, initialMarking, property,
|
|
|
1675
1834
|
}
|
|
1676
1835
|
return { type: "passed", invariant: certificate };
|
|
1677
1836
|
}
|
|
1678
|
-
function vcScript(certificate, flatNet, initialMarking, property, sinkPlaces, invariants) {
|
|
1679
|
-
return script(buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants));
|
|
1837
|
+
function vcScript(certificate, flatNet, initialMarking, property, sinkPlaces, invariants, conditionalSinks = [], stateEquation = false) {
|
|
1838
|
+
return script(buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants, conditionalSinks, stateEquation));
|
|
1680
1839
|
}
|
|
1681
1840
|
function shapeFailure(flatNet, invariants) {
|
|
1682
1841
|
const P = flatNet.places.length;
|
|
@@ -1720,14 +1879,29 @@ function parseVcResults(stdout) {
|
|
|
1720
1879
|
}
|
|
1721
1880
|
return results;
|
|
1722
1881
|
}
|
|
1723
|
-
function buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants) {
|
|
1882
|
+
function buildVerificationConditions(certificate, flatNet, initialMarking, property, sinkPlaces, invariants, conditionalSinks, stateEquation) {
|
|
1724
1883
|
const P = flatNet.places.length;
|
|
1884
|
+
const T = stateEquation ? flatNet.transitions.length : 0;
|
|
1725
1885
|
const mVars = [];
|
|
1726
1886
|
const mpVars = [];
|
|
1727
1887
|
for (let i = 0; i < P; i++) {
|
|
1728
1888
|
mVars.push(`m${i}`);
|
|
1729
1889
|
mpVars.push(`m${i}p`);
|
|
1730
1890
|
}
|
|
1891
|
+
const nVars = [];
|
|
1892
|
+
const npVars = [];
|
|
1893
|
+
for (let k = 0; k < T; k++) {
|
|
1894
|
+
nVars.push(`n${k}`);
|
|
1895
|
+
npVars.push(`n${k}p`);
|
|
1896
|
+
}
|
|
1897
|
+
const candidateOf = (m, n) => {
|
|
1898
|
+
const parts = [`(Reachable ${[...m, ...n].join(" ")})`, ...invariantConditions(invariants, m)];
|
|
1899
|
+
if (T > 0) {
|
|
1900
|
+
for (const v of n) parts.push(`(>= ${v} 0)`);
|
|
1901
|
+
parts.push(...stateEquationConditions(flatNet, initialMarking, n, m));
|
|
1902
|
+
}
|
|
1903
|
+
return conjoin(parts);
|
|
1904
|
+
};
|
|
1731
1905
|
const prelude = [
|
|
1732
1906
|
"; IC3/PDR certificate check (plain SMT-LIB2, not HORN):",
|
|
1733
1907
|
"; each VC below must be unsat for the certificate to stand.",
|
|
@@ -1736,19 +1910,22 @@ function buildVerificationConditions(certificate, flatNet, initialMarking, prope
|
|
|
1736
1910
|
];
|
|
1737
1911
|
for (const v of mVars) prelude.push(`(declare-const ${v} Int)`);
|
|
1738
1912
|
for (const v of mpVars) prelude.push(`(declare-const ${v} Int)`);
|
|
1913
|
+
for (const v of nVars) prelude.push(`(declare-const ${v} Int)`);
|
|
1914
|
+
for (const v of npVars) prelude.push(`(declare-const ${v} Int)`);
|
|
1739
1915
|
const m0 = [];
|
|
1740
1916
|
for (let i = 0; i < P; i++) m0.push(String(initialMarking.tokens(flatNet.places[i])));
|
|
1741
|
-
const
|
|
1742
|
-
const
|
|
1743
|
-
const
|
|
1917
|
+
const n0 = new Array(T).fill("0");
|
|
1918
|
+
const vc1 = [`(assert (not ${candidateOf(m0, n0)}))`];
|
|
1919
|
+
const nonNegative = [...mVars, ...nVars].map((v) => `(assert (>= ${v} 0))`);
|
|
1920
|
+
const step = encodeStepRelationSmt2(flatNet, stateEquation);
|
|
1744
1921
|
const vc2 = [
|
|
1745
1922
|
...nonNegative,
|
|
1746
|
-
`(assert ${
|
|
1923
|
+
`(assert ${candidateOf(mVars, nVars)})`,
|
|
1747
1924
|
`(assert ${step})`,
|
|
1748
|
-
`(assert (not ${
|
|
1925
|
+
`(assert (not ${candidateOf(mpVars, npVars)}))`
|
|
1749
1926
|
];
|
|
1750
|
-
const bad = encodePropertyViolation(flatNet, property, mVars, sinkPlaces, resolveEnvInjection(flatNet));
|
|
1751
|
-
const vc3 = [...nonNegative, `(assert ${
|
|
1927
|
+
const bad = encodePropertyViolation(flatNet, property, mVars, sinkPlaces, resolveEnvInjection(flatNet), conditionalSinks);
|
|
1928
|
+
const vc3 = [...nonNegative, `(assert ${candidateOf(mVars, nVars)})`, `(assert ${bad})`];
|
|
1752
1929
|
return { prelude, asserts: [vc1, vc2, vc3] };
|
|
1753
1930
|
}
|
|
1754
1931
|
function script(vcs) {
|
|
@@ -1812,8 +1989,190 @@ function reasonUnknown(reply) {
|
|
|
1812
1989
|
reason = reason.trim();
|
|
1813
1990
|
return reason === "" ? null : reason;
|
|
1814
1991
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1992
|
+
|
|
1993
|
+
// src/verification/z3/linear-bound.ts
|
|
1994
|
+
function violationDemand(flatNet, property) {
|
|
1995
|
+
const demand = /* @__PURE__ */ new Map();
|
|
1996
|
+
switch (property.type) {
|
|
1997
|
+
case "unreachable":
|
|
1998
|
+
for (const p of property.places) {
|
|
1999
|
+
const pid = flatNet.placeIndex.get(p.name);
|
|
2000
|
+
if (pid != null) demand.set(pid, 1);
|
|
2001
|
+
}
|
|
2002
|
+
break;
|
|
2003
|
+
case "mutual-exclusion": {
|
|
2004
|
+
for (const p of [property.p1, property.p2]) {
|
|
2005
|
+
const pid = flatNet.placeIndex.get(p.name);
|
|
2006
|
+
if (pid != null) demand.set(pid, 1);
|
|
2007
|
+
}
|
|
2008
|
+
break;
|
|
2009
|
+
}
|
|
2010
|
+
case "place-bound":
|
|
2011
|
+
case "branch-place-bound": {
|
|
2012
|
+
const pid = flatNet.placeIndex.get(property.place.name);
|
|
2013
|
+
if (pid != null) demand.set(pid, property.bound + 1);
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
case "deadlock-free":
|
|
2017
|
+
case "terminates-at-sink":
|
|
2018
|
+
case "joined-or-dead-lettered":
|
|
2019
|
+
return null;
|
|
2020
|
+
}
|
|
2021
|
+
return demand.size === 0 ? null : demand;
|
|
2022
|
+
}
|
|
2023
|
+
function zeroWeightPlaces(flatNet) {
|
|
2024
|
+
const zero = new Set(nonlinearPlaces(flatNet));
|
|
2025
|
+
for (const inj of resolveEnvInjection(flatNet)) zero.add(inj.pid);
|
|
2026
|
+
return zero;
|
|
2027
|
+
}
|
|
2028
|
+
function encodeLinearBound(flatNet, initialMarking, property) {
|
|
2029
|
+
const demand = violationDemand(flatNet, property);
|
|
2030
|
+
if (demand == null) return null;
|
|
2031
|
+
const P = flatNet.places.length;
|
|
2032
|
+
const zero = zeroWeightPlaces(flatNet);
|
|
2033
|
+
const lines = [];
|
|
2034
|
+
lines.push("; Linear state-equation bound (VER-015): y >= 0 with y.C <= 0 on every");
|
|
2035
|
+
lines.push("; transition gives y.M <= y.M0 for every reachable M; sat = the violating");
|
|
2036
|
+
lines.push("; markings' demand exceeds that bound, so none is reachable.");
|
|
2037
|
+
lines.push("(set-logic QF_LIA)");
|
|
2038
|
+
for (let p = 0; p < P; p++) lines.push(`(declare-const y${p} Int)`);
|
|
2039
|
+
for (let p = 0; p < P; p++) lines.push(`(assert (>= y${p} 0))`);
|
|
2040
|
+
for (const p of [...zero].sort((a, b) => a - b)) lines.push(`(assert (= y${p} 0))`);
|
|
2041
|
+
for (const ft of flatNet.transitions) {
|
|
2042
|
+
const terms = [];
|
|
2043
|
+
for (let p = 0; p < P; p++) {
|
|
2044
|
+
const c = ft.postVector[p] - ft.preVector[p];
|
|
2045
|
+
if (c !== 0) terms.push(term(c, `y${p}`));
|
|
2046
|
+
}
|
|
2047
|
+
if (terms.length > 0) lines.push(`(assert (<= ${sum(terms)} 0))`);
|
|
2048
|
+
}
|
|
2049
|
+
const demandTerms = [];
|
|
2050
|
+
for (const p of [...demand.keys()].sort((a, b) => a - b)) demandTerms.push(term(demand.get(p), `y${p}`));
|
|
2051
|
+
const initTerms = ["1"];
|
|
2052
|
+
for (let p = 0; p < P; p++) {
|
|
2053
|
+
const m0 = initialMarking.tokens(flatNet.places[p]);
|
|
2054
|
+
if (m0 > 0) initTerms.push(term(m0, `y${p}`));
|
|
2055
|
+
}
|
|
2056
|
+
lines.push(`(assert (>= ${sum(demandTerms)} ${sum(initTerms)}))`);
|
|
2057
|
+
lines.push("(check-sat)");
|
|
2058
|
+
lines.push("(get-model)");
|
|
2059
|
+
return lines.join("\n");
|
|
2060
|
+
}
|
|
2061
|
+
function term(c, v) {
|
|
2062
|
+
if (c === 1) return v;
|
|
2063
|
+
if (c === -1) return `(- ${v})`;
|
|
2064
|
+
return c > 0 ? `(* ${c} ${v})` : `(* (- ${-c}) ${v})`;
|
|
2065
|
+
}
|
|
2066
|
+
function sum(terms) {
|
|
2067
|
+
return terms.length === 1 ? terms[0] : `(+ ${terms.join(" ")})`;
|
|
2068
|
+
}
|
|
2069
|
+
function decodeLinearBound(stdout, placeCount) {
|
|
2070
|
+
const y = new Array(placeCount).fill(0n);
|
|
2071
|
+
let seen = false;
|
|
2072
|
+
for (const def of extractDefineFuns(stdout)) {
|
|
2073
|
+
const m = /^\(define-fun\s+y(\d+)\s+\(\)\s+Int\s+(\(-\s*(\d+)\s*\)|(\d+))\s*\)$/s.exec(def.trim());
|
|
2074
|
+
if (m == null) continue;
|
|
2075
|
+
const pid = Number(m[1]);
|
|
2076
|
+
if (pid >= placeCount) continue;
|
|
2077
|
+
y[pid] = m[3] != null ? -BigInt(m[3]) : BigInt(m[4]);
|
|
2078
|
+
seen = true;
|
|
2079
|
+
}
|
|
2080
|
+
return seen ? y : null;
|
|
2081
|
+
}
|
|
2082
|
+
function checkLinearBoundExact(flatNet, initialMarking, property, y) {
|
|
2083
|
+
const demand = violationDemand(flatNet, property);
|
|
2084
|
+
if (demand == null) return null;
|
|
2085
|
+
const P = flatNet.places.length;
|
|
2086
|
+
if (y.length !== P) return null;
|
|
2087
|
+
const zero = zeroWeightPlaces(flatNet);
|
|
2088
|
+
for (let p = 0; p < P; p++) {
|
|
2089
|
+
if (y[p] < 0n) return null;
|
|
2090
|
+
if (zero.has(p) && y[p] !== 0n) return null;
|
|
2091
|
+
}
|
|
2092
|
+
for (const ft of flatNet.transitions) {
|
|
2093
|
+
let delta = 0n;
|
|
2094
|
+
for (let p = 0; p < P; p++) {
|
|
2095
|
+
if (y[p] === 0n) continue;
|
|
2096
|
+
delta += y[p] * BigInt(ft.postVector[p] - ft.preVector[p]);
|
|
2097
|
+
}
|
|
2098
|
+
if (delta > 0n) return null;
|
|
2099
|
+
}
|
|
2100
|
+
let constant = 0n;
|
|
2101
|
+
for (let p = 0; p < P; p++) {
|
|
2102
|
+
if (y[p] !== 0n) constant += y[p] * BigInt(initialMarking.tokens(flatNet.places[p]));
|
|
2103
|
+
}
|
|
2104
|
+
let demandValue = 0n;
|
|
2105
|
+
for (const [p, d] of demand) demandValue += y[p] * BigInt(d);
|
|
2106
|
+
if (demandValue < constant + 1n) return null;
|
|
2107
|
+
return { weights: y, constant, demandValue };
|
|
2108
|
+
}
|
|
2109
|
+
function formatLinearBound(flatNet, bound) {
|
|
2110
|
+
const parts = [];
|
|
2111
|
+
for (let p = 0; p < bound.weights.length; p++) {
|
|
2112
|
+
const w = bound.weights[p];
|
|
2113
|
+
if (w === 0n) continue;
|
|
2114
|
+
parts.push(w === 1n ? flatNet.places[p].name : `${w}*${flatNet.places[p].name}`);
|
|
2115
|
+
}
|
|
2116
|
+
return `${parts.length === 0 ? "0" : parts.join(" + ")} <= ${bound.constant}`;
|
|
2117
|
+
}
|
|
2118
|
+
function formatLinearDemand(flatNet, property, bound) {
|
|
2119
|
+
const demand = violationDemand(flatNet, property) ?? /* @__PURE__ */ new Map();
|
|
2120
|
+
const parts = [];
|
|
2121
|
+
for (const p of [...demand.keys()].sort((a, b) => a - b)) {
|
|
2122
|
+
const w = bound.weights[p] * BigInt(demand.get(p));
|
|
2123
|
+
if (w === 0n) continue;
|
|
2124
|
+
parts.push(w === 1n ? flatNet.places[p].name : `${w}*${flatNet.places[p].name}`);
|
|
2125
|
+
}
|
|
2126
|
+
return `${parts.length === 0 ? "0" : parts.join(" + ")} >= ${bound.demandValue}`;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// src/verification/graph-decision.ts
|
|
2130
|
+
function decideOverClasses(view, property, sinkPlaces, conditionalSinks = []) {
|
|
2131
|
+
const firstWhere = (pred) => {
|
|
2132
|
+
for (let i = 0; i < view.count; i++) {
|
|
2133
|
+
if (pred(i)) return i;
|
|
2134
|
+
}
|
|
2135
|
+
return -1;
|
|
2136
|
+
};
|
|
2137
|
+
switch (property.type) {
|
|
2138
|
+
case "place-bound":
|
|
2139
|
+
case "branch-place-bound":
|
|
2140
|
+
return firstWhere((i) => view.markingOf(i).tokens(property.place) > property.bound);
|
|
2141
|
+
case "unreachable":
|
|
2142
|
+
return firstWhere((i) => {
|
|
2143
|
+
const m = view.markingOf(i);
|
|
2144
|
+
for (const p of property.places) {
|
|
2145
|
+
if (!m.hasTokens(p)) return false;
|
|
2146
|
+
}
|
|
2147
|
+
return true;
|
|
2148
|
+
});
|
|
2149
|
+
case "mutual-exclusion":
|
|
2150
|
+
return firstWhere((i) => {
|
|
2151
|
+
const m = view.markingOf(i);
|
|
2152
|
+
return m.hasTokens(property.p1) && m.hasTokens(property.p2);
|
|
2153
|
+
});
|
|
2154
|
+
// DeadlockFree (VER-002): a quiescent class that strands a token — some marked
|
|
2155
|
+
// place is not where resting is permitted, the conditional sinks of VER-014
|
|
2156
|
+
// included. The empty marking strands nothing (AC4).
|
|
2157
|
+
case "deadlock-free":
|
|
2158
|
+
return firstWhere((i) => view.isQuiescent(i) && strandsToken(view.markingOf(i), sinkPlaces, conditionalSinks));
|
|
2159
|
+
// TerminatesAtSink (VER-002): a quiescent class that marks NO declared sink.
|
|
2160
|
+
// Inverts with DeadlockFree on the empty marking, by design.
|
|
2161
|
+
case "terminates-at-sink":
|
|
2162
|
+
return firstWhere((i) => view.isQuiescent(i) && !anySinkMarked(view.markingOf(i), sinkPlaces));
|
|
2163
|
+
// JoinedOrDeadLettered (NU-040 AC4): a quiescent class still holding a pending
|
|
2164
|
+
// token. No sink clause.
|
|
2165
|
+
case "joined-or-dead-lettered":
|
|
2166
|
+
return firstWhere((i) => view.isQuiescent(i) && view.markingOf(i).hasTokens(property.pending));
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
function anySinkMarked(m, sinks) {
|
|
2170
|
+
const sinkNames = /* @__PURE__ */ new Set();
|
|
2171
|
+
for (const s of sinks) sinkNames.add(s.name);
|
|
2172
|
+
for (const p of m.placesWithTokens()) {
|
|
2173
|
+
if (sinkNames.has(p.name)) return true;
|
|
2174
|
+
}
|
|
2175
|
+
return false;
|
|
1817
2176
|
}
|
|
1818
2177
|
|
|
1819
2178
|
// src/verification/analysis/dbm.ts
|
|
@@ -1922,6 +2281,35 @@ var DBM = class _DBM {
|
|
|
1922
2281
|
allNames.push(...newClockNames);
|
|
1923
2282
|
return new _DBM(newBounds, newDim, allNames, false).canonicalize();
|
|
1924
2283
|
}
|
|
2284
|
+
/**
|
|
2285
|
+
* The same zone with its clocks reordered: clock `k` of the result is clock
|
|
2286
|
+
* `order[k]` of this DBM. `order` must be a permutation of `0..clockCount()-1`.
|
|
2287
|
+
*
|
|
2288
|
+
* The state-class graph applies this to put every class's clocks in the one
|
|
2289
|
+
* canonical order (VER-010), so two arrivals at the same marking and zone whose
|
|
2290
|
+
* transitions became enabled in a different sequence share a key instead of
|
|
2291
|
+
* being counted as two classes. The reference row and column stay put; the
|
|
2292
|
+
* matrix is copied once, O(dim²) against the O(dim³) canonicalisation every
|
|
2293
|
+
* successor already pays.
|
|
2294
|
+
*/
|
|
2295
|
+
permuted(order) {
|
|
2296
|
+
if (this._empty) return this;
|
|
2297
|
+
const n = this.clockNames.length;
|
|
2298
|
+
const dim = this.dim;
|
|
2299
|
+
const out = new Float64Array(dim * dim);
|
|
2300
|
+
out[0] = 0;
|
|
2301
|
+
const names = new Array(n);
|
|
2302
|
+
for (let i = 0; i < n; i++) {
|
|
2303
|
+
const oi = order[i] + 1;
|
|
2304
|
+
names[i] = this.clockNames[order[i]];
|
|
2305
|
+
out[(i + 1) * dim] = this.bounds[oi * dim];
|
|
2306
|
+
out[i + 1] = this.bounds[oi];
|
|
2307
|
+
for (let j = 0; j < n; j++) {
|
|
2308
|
+
out[(i + 1) * dim + (j + 1)] = this.bounds[oi * dim + (order[j] + 1)];
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
return new _DBM(out, dim, names, false);
|
|
2312
|
+
}
|
|
1925
2313
|
/** Lets time pass: set all lower bounds to 0. */
|
|
1926
2314
|
letTimePass() {
|
|
1927
2315
|
if (this._empty) return this;
|
|
@@ -1953,6 +2341,24 @@ var DBM = class _DBM {
|
|
|
1953
2341
|
}
|
|
1954
2342
|
return true;
|
|
1955
2343
|
}
|
|
2344
|
+
/**
|
|
2345
|
+
* The zone's identity for state-class dedup: the clock names and the FULL
|
|
2346
|
+
* canonical matrix, every difference bound included.
|
|
2347
|
+
*
|
|
2348
|
+
* {@link toString} prints only the per-clock projections `[lo, hi]`, and two
|
|
2349
|
+
* zones can agree on every projection while disagreeing on a difference
|
|
2350
|
+
* constraint `θi - θj <= c` — the class where one transition must fire no later
|
|
2351
|
+
* than another versus the class where either may go first. Keying on the
|
|
2352
|
+
* projections merges those, and since the graph explores only the first
|
|
2353
|
+
* arrival's successors, a marking reachable only from the second is lost: a
|
|
2354
|
+
* false `proven`. This key is what {@link equals} compares, rendered.
|
|
2355
|
+
*/
|
|
2356
|
+
zoneKey() {
|
|
2357
|
+
if (this._empty) return "DBM[empty]";
|
|
2358
|
+
const parts = [this.clockNames.join(",")];
|
|
2359
|
+
for (let i = 0; i < this.bounds.length; i++) parts.push(formatBound(this.bounds[i]));
|
|
2360
|
+
return parts.join("|");
|
|
2361
|
+
}
|
|
1956
2362
|
toString() {
|
|
1957
2363
|
if (this._empty) return "DBM[empty]";
|
|
1958
2364
|
const parts = [];
|
|
@@ -2201,10 +2607,35 @@ var StateClassGraph = class _StateClassGraph {
|
|
|
2201
2607
|
}
|
|
2202
2608
|
};
|
|
2203
2609
|
function classKey(sc) {
|
|
2204
|
-
return `${sc.marking.toString()}|${sc.firingDomain.
|
|
2610
|
+
return `${sc.marking.toString()}|${sc.firingDomain.zoneKey()}`;
|
|
2611
|
+
}
|
|
2612
|
+
function canonicalOrder(transitions) {
|
|
2613
|
+
let sorted = true;
|
|
2614
|
+
for (let i = 1; i < transitions.length; i++) {
|
|
2615
|
+
if (transitions[i].name < transitions[i - 1].name) {
|
|
2616
|
+
sorted = false;
|
|
2617
|
+
break;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
if (sorted) return null;
|
|
2621
|
+
const order = new Array(transitions.length);
|
|
2622
|
+
for (let i = 0; i < order.length; i++) order[i] = i;
|
|
2623
|
+
order.sort((a, b) => {
|
|
2624
|
+
const na = transitions[a].name;
|
|
2625
|
+
const nb = transitions[b].name;
|
|
2626
|
+
return na < nb ? -1 : na > nb ? 1 : a - b;
|
|
2627
|
+
});
|
|
2628
|
+
return order;
|
|
2629
|
+
}
|
|
2630
|
+
function permute(items, order) {
|
|
2631
|
+
const out = new Array(items.length);
|
|
2632
|
+
for (let i = 0; i < order.length; i++) out[i] = items[order[i]];
|
|
2633
|
+
return out;
|
|
2205
2634
|
}
|
|
2206
2635
|
function initialStateClass(net, initialMarking, envPlaces, envMode) {
|
|
2207
|
-
const
|
|
2636
|
+
const found = findEnabledTransitions(net, initialMarking, envPlaces, envMode);
|
|
2637
|
+
const order = canonicalOrder(found);
|
|
2638
|
+
const enabledTransitions = order === null ? found : permute(found, order);
|
|
2208
2639
|
const clockNames = enabledTransitions.map((t) => t.name);
|
|
2209
2640
|
const lowerBounds = enabledTransitions.map((t) => earliest(t.timing) / 1e3);
|
|
2210
2641
|
const upperBounds = enabledTransitions.map((t) => latest(t.timing) / 1e3);
|
|
@@ -2249,14 +2680,19 @@ function computeSuccessor(net, current, fired, environmentPlaces, environmentMod
|
|
|
2249
2680
|
const newClockNames = newlyEnabled.map((t) => t.name);
|
|
2250
2681
|
const newLowerBounds = newlyEnabled.map((t) => earliest(t.timing) / 1e3);
|
|
2251
2682
|
const newUpperBounds = newlyEnabled.map((t) => latest(t.timing) / 1e3);
|
|
2252
|
-
|
|
2683
|
+
let firedDBM = current.firingDomain.fireTransition(
|
|
2253
2684
|
firedIdx,
|
|
2254
2685
|
newClockNames,
|
|
2255
2686
|
newLowerBounds,
|
|
2256
2687
|
newUpperBounds,
|
|
2257
2688
|
persistentIndices
|
|
2258
2689
|
);
|
|
2259
|
-
|
|
2690
|
+
let allEnabled = [...persistent, ...newlyEnabled];
|
|
2691
|
+
const order = canonicalOrder(allEnabled);
|
|
2692
|
+
if (order !== null) {
|
|
2693
|
+
allEnabled = permute(allEnabled, order);
|
|
2694
|
+
firedDBM = firedDBM.permuted(order);
|
|
2695
|
+
}
|
|
2260
2696
|
const readyEarliest = allEnabled.map((_, k) => firedDBM.getLowerBound(k));
|
|
2261
2697
|
const newDBM = firedDBM.letTimePass();
|
|
2262
2698
|
return new StateClass(newMarking, newDBM, allEnabled, readyEarliest);
|
|
@@ -2328,10 +2764,7 @@ function fireTransition(marking, transition, outputPlaces, environmentPlaces, en
|
|
|
2328
2764
|
consumeFromPlace(builder, spec.place, toConsume, environmentPlaces, environmentMode);
|
|
2329
2765
|
}
|
|
2330
2766
|
for (const arc of transition.resets) {
|
|
2331
|
-
|
|
2332
|
-
if (current > 0) {
|
|
2333
|
-
builder.removeTokens(arc.place, current);
|
|
2334
|
-
}
|
|
2767
|
+
builder.tokens(arc.place, 0);
|
|
2335
2768
|
}
|
|
2336
2769
|
for (const place of outputPlaces) {
|
|
2337
2770
|
builder.addTokens(place, 1);
|
|
@@ -2348,12 +2781,72 @@ function consumeFromPlace(builder, place, count, environmentPlaces, environmentM
|
|
|
2348
2781
|
}
|
|
2349
2782
|
}
|
|
2350
2783
|
|
|
2784
|
+
// src/verification/scg-verifier.ts
|
|
2785
|
+
function isUntimed(net) {
|
|
2786
|
+
for (const t of net.transitions) {
|
|
2787
|
+
if (t.timing.type !== "immediate") return false;
|
|
2788
|
+
}
|
|
2789
|
+
return true;
|
|
2790
|
+
}
|
|
2791
|
+
var NOTE_ENUMERATED = "\nNote: decided by bounded state-space enumeration \u2014 the state-class graph closed, so the verdict is sound AND complete: a `violated` is a real firing sequence, not a possibly-spurious over-approximation. The net is untimed, so this is the same claim the encoders make (VER-017).\n";
|
|
2792
|
+
function verifyViaStateClassGraph(net, initial, property, sinkPlaces, maxClasses, conditionalSinks = []) {
|
|
2793
|
+
const graph = StateClassGraph.build(net, initial, maxClasses);
|
|
2794
|
+
const classes = graph.stateClasses();
|
|
2795
|
+
if (!graph.isComplete()) return { kind: "truncated", classCount: classes.length };
|
|
2796
|
+
const violating = decideOverClasses(
|
|
2797
|
+
{
|
|
2798
|
+
count: classes.length,
|
|
2799
|
+
markingOf: (i) => classes[i].marking,
|
|
2800
|
+
isQuiescent: (i) => graph.successors(classes[i]).size === 0
|
|
2801
|
+
},
|
|
2802
|
+
property,
|
|
2803
|
+
sinkPlaces,
|
|
2804
|
+
conditionalSinks
|
|
2805
|
+
);
|
|
2806
|
+
if (violating >= 0) {
|
|
2807
|
+
const [trace, transitions] = counterexamplePath(graph, classes[violating]);
|
|
2808
|
+
return { kind: "decided", verdict: { type: "violated" }, trace, transitions, classCount: classes.length };
|
|
2809
|
+
}
|
|
2810
|
+
return {
|
|
2811
|
+
kind: "decided",
|
|
2812
|
+
verdict: { type: "proven", method: "state-space enumeration (VER-017)", inductiveInvariant: null },
|
|
2813
|
+
trace: [],
|
|
2814
|
+
transitions: [],
|
|
2815
|
+
classCount: classes.length
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
function counterexamplePath(graph, target) {
|
|
2819
|
+
const parent = /* @__PURE__ */ new Map();
|
|
2820
|
+
const via = /* @__PURE__ */ new Map();
|
|
2821
|
+
const seen = /* @__PURE__ */ new Set([graph.initialClass]);
|
|
2822
|
+
const queue = [graph.initialClass];
|
|
2823
|
+
while (queue.length > 0) {
|
|
2824
|
+
const current = queue.shift();
|
|
2825
|
+
if (current === target) break;
|
|
2826
|
+
for (const [transition, edges] of graph.outgoingBranchEdges(current)) {
|
|
2827
|
+
for (const edge of edges) {
|
|
2828
|
+
if (seen.has(edge.target)) continue;
|
|
2829
|
+
seen.add(edge.target);
|
|
2830
|
+
parent.set(edge.target, current);
|
|
2831
|
+
via.set(edge.target, transition.name);
|
|
2832
|
+
queue.push(edge.target);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
const chain = [];
|
|
2837
|
+
for (let cur = target; cur != null; cur = parent.get(cur)) {
|
|
2838
|
+
chain.push(cur);
|
|
2839
|
+
}
|
|
2840
|
+
chain.reverse();
|
|
2841
|
+
return [chain.map((sc) => sc.marking), chain.slice(1).map((sc) => via.get(sc))];
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2351
2844
|
// src/verification/z3/counterexample-decoder.ts
|
|
2352
|
-
function decode(answer, flatNet) {
|
|
2353
|
-
const states = decodeStateSet(answer, flatNet);
|
|
2845
|
+
function decode(answer, flatNet, counterCount = 0) {
|
|
2846
|
+
const states = decodeStateSet(answer, flatNet, counterCount);
|
|
2354
2847
|
return { states, note: states.size === 0 ? "no ground Reachable states in the z3 proof" : null };
|
|
2355
2848
|
}
|
|
2356
|
-
function decodeStateSet(answer, flatNet) {
|
|
2849
|
+
function decodeStateSet(answer, flatNet, counterCount = 0) {
|
|
2357
2850
|
const byKey = /* @__PURE__ */ new Map();
|
|
2358
2851
|
const P = flatNet.places.length;
|
|
2359
2852
|
for (const head of ["(Reachable", "(|Reachable|"]) {
|
|
@@ -2370,8 +2863,8 @@ function decodeStateSet(answer, flatNet) {
|
|
|
2370
2863
|
if (end < 0) break;
|
|
2371
2864
|
const inner = answer.slice(start + head.length, end - 1);
|
|
2372
2865
|
const args = parseGroundIntArgs(inner);
|
|
2373
|
-
if (args != null && args.length === P) {
|
|
2374
|
-
const marking = toMarking(args, flatNet);
|
|
2866
|
+
if (args != null && args.length === P + counterCount) {
|
|
2867
|
+
const marking = toMarking(counterCount === 0 ? args : args.slice(0, P), flatNet);
|
|
2375
2868
|
const key = marking.toString();
|
|
2376
2869
|
if (!byKey.has(key)) byKey.set(key, marking);
|
|
2377
2870
|
}
|
|
@@ -2559,16 +3052,19 @@ function sinkIndices(flatNet, sinkPlaces) {
|
|
|
2559
3052
|
}
|
|
2560
3053
|
return idx;
|
|
2561
3054
|
}
|
|
2562
|
-
function satisfiesBadIndexed(index, state, property, sinkPlaces) {
|
|
3055
|
+
function satisfiesBadIndexed(index, state, property, sinkPlaces, conditionalSinks) {
|
|
2563
3056
|
const flatNet = index.flatNet;
|
|
2564
3057
|
switch (property.type) {
|
|
2565
|
-
// DeadlockFree (VER-002): quiescent AND some marked place is not
|
|
2566
|
-
// sink
|
|
3058
|
+
// DeadlockFree (VER-002): quiescent AND some marked place is not where resting
|
|
3059
|
+
// is permitted — a conditional sink (VER-014) counts only while every marker
|
|
3060
|
+
// that would excuse it is unmarked. Mirrors the encoder's `stranded` disjunction.
|
|
2567
3061
|
case "deadlock-free": {
|
|
2568
3062
|
if (!isQuiescent(index, state)) return false;
|
|
2569
|
-
const
|
|
3063
|
+
const excuses = strandingExcuses(flatNet, sinkPlaces, conditionalSinks);
|
|
2570
3064
|
for (let pid = 0; pid < flatNet.places.length; pid++) {
|
|
2571
|
-
|
|
3065
|
+
const markers = excuses[pid];
|
|
3066
|
+
if (markers == null || state[pid] < 1) continue;
|
|
3067
|
+
if (markers.every((k) => state[k] === 0)) return true;
|
|
2572
3068
|
}
|
|
2573
3069
|
return false;
|
|
2574
3070
|
}
|
|
@@ -2611,7 +3107,7 @@ function satisfiesBadIndexed(index, state, property, sinkPlaces) {
|
|
|
2611
3107
|
}
|
|
2612
3108
|
}
|
|
2613
3109
|
}
|
|
2614
|
-
function replayCounterexample(flatNet, initial, decodedStates, property, sinkPlaces, options = {}) {
|
|
3110
|
+
function replayCounterexample(flatNet, initial, decodedStates, property, sinkPlaces, options = {}, conditionalSinks = []) {
|
|
2615
3111
|
const segmentBudget = options.segmentBudget ?? 3;
|
|
2616
3112
|
const nodeBudget = options.nodeBudget ?? 1e4;
|
|
2617
3113
|
const anchors = /* @__PURE__ */ new Set();
|
|
@@ -2628,7 +3124,7 @@ function replayCounterexample(flatNet, initial, decodedStates, property, sinkPla
|
|
|
2628
3124
|
};
|
|
2629
3125
|
}
|
|
2630
3126
|
const index = buildIndex(flatNet);
|
|
2631
|
-
if (satisfiesBadIndexed(index, initial, property, sinkPlaces)) {
|
|
3127
|
+
if (satisfiesBadIndexed(index, initial, property, sinkPlaces, conditionalSinks)) {
|
|
2632
3128
|
return { kind: "confirmed", states: [initial], steps: [], nodesExplored: 1 };
|
|
2633
3129
|
}
|
|
2634
3130
|
const nodes = [{ state: initial, step: null, parent: -1, segment: 0 }];
|
|
@@ -2657,7 +3153,7 @@ function replayCounterexample(flatNet, initial, decodedStates, property, sinkPla
|
|
|
2657
3153
|
}
|
|
2658
3154
|
nodes.push({ state: succ.state, step: succ.step, parent: idx, segment });
|
|
2659
3155
|
const childIdx = nodes.length - 1;
|
|
2660
|
-
if (satisfiesBadIndexed(index, succ.state, property, sinkPlaces)) {
|
|
3156
|
+
if (satisfiesBadIndexed(index, succ.state, property, sinkPlaces, conditionalSinks)) {
|
|
2661
3157
|
const chain = reconstruct(nodes, childIdx);
|
|
2662
3158
|
return { kind: "confirmed", ...chain, nodesExplored: nodes.length };
|
|
2663
3159
|
}
|
|
@@ -2805,7 +3301,7 @@ function buildLayout(plan, P) {
|
|
|
2805
3301
|
function quantified2(names) {
|
|
2806
3302
|
return names.map((v) => `(${v} Int)`).join(" ");
|
|
2807
3303
|
}
|
|
2808
|
-
function encodeColoured(plan, flat, initial, property, invariants, sinkPlaces) {
|
|
3304
|
+
function encodeColoured(plan, flat, initial, property, invariants, sinkPlaces, conditionalSinks = []) {
|
|
2809
3305
|
const P = flat.places.length;
|
|
2810
3306
|
const k = plan.k;
|
|
2811
3307
|
const lay = buildLayout(plan, P);
|
|
@@ -2874,13 +3370,13 @@ function encodeColoured(plan, flat, initial, property, invariants, sinkPlaces) {
|
|
|
2874
3370
|
}
|
|
2875
3371
|
}
|
|
2876
3372
|
lines.push("");
|
|
2877
|
-
const error = encodeError(plan, lay, flat, property, sinkPlaces, injectionMap(flat));
|
|
3373
|
+
const error = encodeError(plan, lay, flat, property, sinkPlaces, injectionMap(flat), conditionalSinks);
|
|
2878
3374
|
if (error == null) return null;
|
|
2879
3375
|
lines.push(error);
|
|
2880
3376
|
lines.push("");
|
|
2881
3377
|
lines.push("(assert (not Error))");
|
|
2882
3378
|
lines.push("(check-sat)");
|
|
2883
|
-
return { smt2: lines.join("\n"), placeCount: P };
|
|
3379
|
+
return { smt2: lines.join("\n"), placeCount: P, counterCount: 0 };
|
|
2884
3380
|
}
|
|
2885
3381
|
function encodeRule(plan, lay, invariants, fill) {
|
|
2886
3382
|
const enab = [];
|
|
@@ -2942,17 +3438,17 @@ function liftedInvariant(inv, plan, lay, names) {
|
|
|
2942
3438
|
terms.push(w === 1 ? agg : `(* ${w} ${agg})`);
|
|
2943
3439
|
}
|
|
2944
3440
|
if (terms.length === 0) return null;
|
|
2945
|
-
const
|
|
2946
|
-
return `(= ${
|
|
3441
|
+
const sum2 = terms.length === 1 ? terms[0] : `(+ ${terms.join(" ")})`;
|
|
3442
|
+
return `(= ${sum2} ${inv.constant})`;
|
|
2947
3443
|
}
|
|
2948
|
-
function encodeError(plan, lay, flat, property, sinkPlaces, envInj) {
|
|
2949
|
-
const violation = encodeViolation(plan, lay, flat, property, sinkPlaces, envInj);
|
|
3444
|
+
function encodeError(plan, lay, flat, property, sinkPlaces, envInj, conditionalSinks) {
|
|
3445
|
+
const violation = encodeViolation(plan, lay, flat, property, sinkPlaces, envInj, conditionalSinks);
|
|
2950
3446
|
if (violation == null) return null;
|
|
2951
3447
|
return `(assert (forall (${quantified2(lay.cur)})
|
|
2952
3448
|
(=> (and (Reachable ${lay.cur.join(" ")}) ${violation})
|
|
2953
3449
|
Error)))`;
|
|
2954
3450
|
}
|
|
2955
|
-
function encodeViolation(plan, lay, flat, property, sinkPlaces, envInj) {
|
|
3451
|
+
function encodeViolation(plan, lay, flat, property, sinkPlaces, envInj, conditionalSinks) {
|
|
2956
3452
|
const anyPlacePresent = (places) => {
|
|
2957
3453
|
const conds = indexOrdered(flat, places).map((pid) => `(>= ${aggregate(plan, lay, pid, lay.cur)} 1)`);
|
|
2958
3454
|
return conds.length === 0 ? "false" : `(and ${conds.join(" ")})`;
|
|
@@ -2968,16 +3464,15 @@ function encodeViolation(plan, lay, flat, property, sinkPlaces, envInj) {
|
|
|
2968
3464
|
return anyPlacePresent([property.p1, property.p2]);
|
|
2969
3465
|
case "unreachable":
|
|
2970
3466
|
return anyPlacePresent(property.places);
|
|
2971
|
-
// DeadlockFree (VER-002): quiescent AND some marked place is not
|
|
2972
|
-
//
|
|
3467
|
+
// DeadlockFree (VER-002): quiescent AND some marked place is not where resting
|
|
3468
|
+
// is permitted (VER-014). Mirrors the flat encoder's `stranded` disjunction over
|
|
3469
|
+
// the aggregate (all-colour) count of each place.
|
|
2973
3470
|
case "deadlock-free": {
|
|
2974
3471
|
const conds = encodeColouredQuiescent(plan, lay, flat, envInj);
|
|
2975
3472
|
if (conds == null) return "false";
|
|
2976
|
-
const
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
if (!sinks.has(pid)) stranded.push(`(>= ${aggregate(plan, lay, pid, lay.cur)} 1)`);
|
|
2980
|
-
}
|
|
3473
|
+
const counts = [];
|
|
3474
|
+
for (let pid = 0; pid < flat.places.length; pid++) counts.push(aggregate(plan, lay, pid, lay.cur));
|
|
3475
|
+
const stranded = strandedConditions(strandingExcuses(flat, sinkPlaces, conditionalSinks), counts);
|
|
2981
3476
|
if (stranded.length === 0) return "false";
|
|
2982
3477
|
conds.push(`(or ${stranded.join(" ")})`);
|
|
2983
3478
|
return joinColoured(conds);
|
|
@@ -3071,8 +3566,8 @@ function encodeColouredQuiescent(plan, lay, flat, envInj) {
|
|
|
3071
3566
|
disabledConditions.push("true");
|
|
3072
3567
|
continue;
|
|
3073
3568
|
}
|
|
3074
|
-
const
|
|
3075
|
-
if (
|
|
3569
|
+
const term2 = colouredDisabledTerm(cls, plan, lay);
|
|
3570
|
+
if (term2 != null) reasons.push(term2);
|
|
3076
3571
|
if (reasons.length === 0) return null;
|
|
3077
3572
|
disabledConditions.push(reasons.length === 1 ? reasons[0] : `(or ${reasons.join(" ")})`);
|
|
3078
3573
|
}
|
|
@@ -3268,7 +3763,7 @@ var NameStateClass = class {
|
|
|
3268
3763
|
}
|
|
3269
3764
|
};
|
|
3270
3765
|
function baseKeyOf(base) {
|
|
3271
|
-
return `${base.marking.toString()}|${base.firingDomain.
|
|
3766
|
+
return `${base.marking.toString()}|${base.firingDomain.zoneKey()}`;
|
|
3272
3767
|
}
|
|
3273
3768
|
|
|
3274
3769
|
// src/verification/analysis/name-state-class-graph.ts
|
|
@@ -3499,7 +3994,7 @@ function enablingSymbols(names, colouredIn) {
|
|
|
3499
3994
|
|
|
3500
3995
|
// src/verification/nu-scg-verifier.ts
|
|
3501
3996
|
var NOTE_EXACT = "\nNote: \u03BD-join correlation decided exactly via the state-class-graph name-partition quotient \u2014 the symbolic graph closed, so the verdict is sound AND complete (no spurious different-name counterexample; quiescence is name-aware), beyond the bounded-budget fragment (NU-050, Route B).\n";
|
|
3502
|
-
function verifyViaNameScg(net, initial, property, sinkPlaces, environmentPlaces, environmentMode, maxClasses, fragmentMode, carrierPlaces, prioritySemantics) {
|
|
3997
|
+
function verifyViaNameScg(net, initial, property, sinkPlaces, environmentPlaces, environmentMode, maxClasses, fragmentMode, carrierPlaces, prioritySemantics, conditionalSinks = []) {
|
|
3503
3998
|
const fragment = classify(net, fragmentMode, carrierPlaces);
|
|
3504
3999
|
if (fragment === null) return null;
|
|
3505
4000
|
for (const p of initial.placesWithTokens()) {
|
|
@@ -3526,9 +4021,9 @@ function verifyViaNameScg(net, initial, property, sinkPlaces, environmentPlaces,
|
|
|
3526
4021
|
classCount: scg.classCount()
|
|
3527
4022
|
};
|
|
3528
4023
|
}
|
|
3529
|
-
const violating = decide(scg, property, sinkPlaces);
|
|
4024
|
+
const violating = decide(scg, property, sinkPlaces, conditionalSinks);
|
|
3530
4025
|
if (violating >= 0) {
|
|
3531
|
-
const [trace, transitions] =
|
|
4026
|
+
const [trace, transitions] = counterexamplePath2(scg, violating);
|
|
3532
4027
|
return { verdict: { type: "violated" }, trace, transitions, note: NOTE_EXACT, classCount: scg.classCount() };
|
|
3533
4028
|
}
|
|
3534
4029
|
return {
|
|
@@ -3539,61 +4034,19 @@ function verifyViaNameScg(net, initial, property, sinkPlaces, environmentPlaces,
|
|
|
3539
4034
|
classCount: scg.classCount()
|
|
3540
4035
|
};
|
|
3541
4036
|
}
|
|
3542
|
-
function decide(scg, property, sinkPlaces) {
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
case "unreachable":
|
|
3554
|
-
return firstWhere((i) => {
|
|
3555
|
-
const m = scg.markingOf(i);
|
|
3556
|
-
for (const p of property.places) {
|
|
3557
|
-
if (!m.hasTokens(p)) return false;
|
|
3558
|
-
}
|
|
3559
|
-
return true;
|
|
3560
|
-
});
|
|
3561
|
-
case "mutual-exclusion":
|
|
3562
|
-
return firstWhere((i) => {
|
|
3563
|
-
const m = scg.markingOf(i);
|
|
3564
|
-
return m.hasTokens(property.p1) && m.hasTokens(property.p2);
|
|
3565
|
-
});
|
|
3566
|
-
// DeadlockFree (VER-002): a quiescent class that strands a token — some marked
|
|
3567
|
-
// place is not a declared sink. The empty marking strands nothing (AC4).
|
|
3568
|
-
case "deadlock-free":
|
|
3569
|
-
return firstWhere((i) => scg.successorsOf(i).length === 0 && !allTokensInSinks(scg.markingOf(i), sinkPlaces));
|
|
3570
|
-
// TerminatesAtSink (VER-002): a quiescent class that marks NO declared sink.
|
|
3571
|
-
// Inverts with DeadlockFree on the empty marking, by design.
|
|
3572
|
-
case "terminates-at-sink":
|
|
3573
|
-
return firstWhere((i) => scg.successorsOf(i).length === 0 && !anySinkMarked(scg.markingOf(i), sinkPlaces));
|
|
3574
|
-
// JoinedOrDeadLettered (NU-040 AC4): a quiescent class still holding a pending
|
|
3575
|
-
// token. No sink clause.
|
|
3576
|
-
case "joined-or-dead-lettered":
|
|
3577
|
-
return firstWhere((i) => scg.successorsOf(i).length === 0 && scg.markingOf(i).hasTokens(property.pending));
|
|
3578
|
-
}
|
|
3579
|
-
}
|
|
3580
|
-
function allTokensInSinks(m, sinks) {
|
|
3581
|
-
const sinkNames = /* @__PURE__ */ new Set();
|
|
3582
|
-
for (const s of sinks) sinkNames.add(s.name);
|
|
3583
|
-
for (const p of m.placesWithTokens()) {
|
|
3584
|
-
if (!sinkNames.has(p.name)) return false;
|
|
3585
|
-
}
|
|
3586
|
-
return true;
|
|
3587
|
-
}
|
|
3588
|
-
function anySinkMarked(m, sinks) {
|
|
3589
|
-
const sinkNames = /* @__PURE__ */ new Set();
|
|
3590
|
-
for (const s of sinks) sinkNames.add(s.name);
|
|
3591
|
-
for (const p of m.placesWithTokens()) {
|
|
3592
|
-
if (sinkNames.has(p.name)) return true;
|
|
3593
|
-
}
|
|
3594
|
-
return false;
|
|
4037
|
+
function decide(scg, property, sinkPlaces, conditionalSinks) {
|
|
4038
|
+
return decideOverClasses(
|
|
4039
|
+
{
|
|
4040
|
+
count: scg.classCount(),
|
|
4041
|
+
markingOf: (i) => scg.markingOf(i),
|
|
4042
|
+
isQuiescent: (i) => scg.successorsOf(i).length === 0
|
|
4043
|
+
},
|
|
4044
|
+
property,
|
|
4045
|
+
sinkPlaces,
|
|
4046
|
+
conditionalSinks
|
|
4047
|
+
);
|
|
3595
4048
|
}
|
|
3596
|
-
function
|
|
4049
|
+
function counterexamplePath2(scg, target) {
|
|
3597
4050
|
const n = scg.classCount();
|
|
3598
4051
|
const parent = new Array(n).fill(-1);
|
|
3599
4052
|
const via = new Array(n).fill("");
|
|
@@ -3633,13 +4086,17 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3633
4086
|
_property = deadlockFree();
|
|
3634
4087
|
_environmentPlaces = /* @__PURE__ */ new Set();
|
|
3635
4088
|
_sinkPlaces = /* @__PURE__ */ new Set();
|
|
4089
|
+
_conditionalSinks = [];
|
|
3636
4090
|
_budgetPlaces = /* @__PURE__ */ new Set();
|
|
3637
4091
|
_environmentMode = alwaysAvailable();
|
|
3638
4092
|
_timeoutMs = 6e4;
|
|
3639
4093
|
_certificateCheck = true;
|
|
3640
4094
|
_counterexampleReplay = true;
|
|
3641
4095
|
_semiflowInvariants = false;
|
|
4096
|
+
_stateEquation = false;
|
|
4097
|
+
_linearBound = true;
|
|
3642
4098
|
_nuMaxClasses = 1e5;
|
|
4099
|
+
_enumerationMaxClasses = 5e4;
|
|
3643
4100
|
_fragmentMode = "base";
|
|
3644
4101
|
_carrierPlaces = /* @__PURE__ */ new Set();
|
|
3645
4102
|
_prioritySemantics = "none";
|
|
@@ -3669,13 +4126,47 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3669
4126
|
return this;
|
|
3670
4127
|
}
|
|
3671
4128
|
/**
|
|
3672
|
-
* Declares expected sink (terminal) places for deadlock-freedom analysis
|
|
3673
|
-
*
|
|
4129
|
+
* Declares expected sink (terminal) places for deadlock-freedom analysis
|
|
4130
|
+
* (VER-002): a token resting in one is never stranded, and `TerminatesAtSink`
|
|
4131
|
+
* asks whether one of them was reached.
|
|
3674
4132
|
*/
|
|
3675
4133
|
sinkPlaces(...places) {
|
|
3676
4134
|
for (const p of places) this._sinkPlaces.add(p);
|
|
3677
4135
|
return this;
|
|
3678
4136
|
}
|
|
4137
|
+
/**
|
|
4138
|
+
* Declares places where a token may rest **while `marker` holds a token**
|
|
4139
|
+
* (VER-014) — a designed terminal such as a halt or pause marker, under which
|
|
4140
|
+
* the work it interrupted legitimately stays where it was delivered.
|
|
4141
|
+
*
|
|
4142
|
+
* `DeadlockFree` then reads a quiescent marking against the union of the
|
|
4143
|
+
* declared sinks, the markers, and every conditional set whose marker is marked:
|
|
4144
|
+
* a token in `p` is stranded only when none of those excuse it. The marker
|
|
4145
|
+
* itself is at rest whenever it is marked, so `sinkPlacesWhen(halt)` with no
|
|
4146
|
+
* further places excuses exactly the halt token. Repeated calls for one marker
|
|
4147
|
+
* accumulate; declarations for several markers union. `TerminatesAtSink` is
|
|
4148
|
+
* unaffected and reads only {@link sinkPlaces}.
|
|
4149
|
+
*
|
|
4150
|
+
* ```ts
|
|
4151
|
+
* SmtVerifier.forNet(net)
|
|
4152
|
+
* .property(deadlockFree())
|
|
4153
|
+
* .sinkPlaces(done) // may always rest
|
|
4154
|
+
* .sinkPlacesWhen(halt, inbox, pending) // may rest once the run halted
|
|
4155
|
+
* .sinkPlacesWhen(pause, inbox) // may rest while paused
|
|
4156
|
+
* ```
|
|
4157
|
+
*
|
|
4158
|
+
* An unresolved marker or place contributes nothing, as an unresolved sink
|
|
4159
|
+
* does: a mistyped marker makes the property stricter, never laxer.
|
|
4160
|
+
*/
|
|
4161
|
+
sinkPlacesWhen(marker, ...places) {
|
|
4162
|
+
let entry = this._conditionalSinks.find((c) => c.marker.name === marker.name);
|
|
4163
|
+
if (entry == null) {
|
|
4164
|
+
entry = { marker, places: /* @__PURE__ */ new Set() };
|
|
4165
|
+
this._conditionalSinks.push(entry);
|
|
4166
|
+
}
|
|
4167
|
+
for (const p of places) entry.places.add(p);
|
|
4168
|
+
return this;
|
|
4169
|
+
}
|
|
3679
4170
|
/**
|
|
3680
4171
|
* Declares ν-net budget places (NU-040): places whose token count bounds the
|
|
3681
4172
|
* live correlation pool (they gate fresh-name minting). Declaring at least one
|
|
@@ -3753,10 +4244,67 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3753
4244
|
* `Certificate check: not applicable (name-coloured encoding)`. Off by default so
|
|
3754
4245
|
* reports stay byte-equal.
|
|
3755
4246
|
*/
|
|
4247
|
+
/**
|
|
4248
|
+
* `'auto'` decides whether the semiflows would add **information to the
|
|
4249
|
+
* encoding**, which is not the same question as whether they would appear in
|
|
4250
|
+
* {@link SmtVerificationResult.invariants} for a caller who reads them.
|
|
4251
|
+
*
|
|
4252
|
+
* A complete basis spans every conservation law of the net, so a semiflow it
|
|
4253
|
+
* spans constrains nothing further and IC3 gains nothing from it — that is why
|
|
4254
|
+
* `'auto'` skips the enumeration there. But the basis is the *signed*
|
|
4255
|
+
* null-space, and a law it spans need not appear in it in **non-negative**
|
|
4256
|
+
* form; only the Farkas enumeration produces that. A caller inspecting the
|
|
4257
|
+
* invariant list for a law of a given shape — "a non-negative law weighting the
|
|
4258
|
+
* budget place and every running place positively" — can therefore find nothing
|
|
4259
|
+
* on a net that plainly has one. Such a caller should ask for the union
|
|
4260
|
+
* explicitly: `'auto'` is the setting to prefer for verification, not for
|
|
4261
|
+
* harvesting.
|
|
4262
|
+
*/
|
|
3756
4263
|
semiflowInvariants(enabled) {
|
|
3757
4264
|
this._semiflowInvariants = enabled;
|
|
3758
4265
|
return this;
|
|
3759
4266
|
}
|
|
4267
|
+
/**
|
|
4268
|
+
* Enables/disables the linear state-equation bound phase (VER-015; default:
|
|
4269
|
+
* enabled). A reachability-safety property whose violating markings exceed some
|
|
4270
|
+
* `y·M <= y·M0` with `y >= 0`, `y·C <= 0` is then proven structurally, from one
|
|
4271
|
+
* linear query re-checked in exact integer arithmetic, before any fixpoint search.
|
|
4272
|
+
* Disable it to force the IC3/PDR path — for its certificate, or to exercise the
|
|
4273
|
+
* fixpoint engine itself.
|
|
4274
|
+
*/
|
|
4275
|
+
linearBound(enabled) {
|
|
4276
|
+
this._linearBound = enabled;
|
|
4277
|
+
return this;
|
|
4278
|
+
}
|
|
4279
|
+
/**
|
|
4280
|
+
* Encodes the **state equation** with firing counters (VER-016; default:
|
|
4281
|
+
* disabled — the encoding then carries places only).
|
|
4282
|
+
*
|
|
4283
|
+
* The flat encoding gains one counter `n_t` per flat transition and every
|
|
4284
|
+
* transition rule conjoins the marking equation `M' = M0 + C·n'` for each place
|
|
4285
|
+
* whose column is exact (no consume-all / reset arc, not injected). Every linear
|
|
4286
|
+
* consequence of the marking equation — the equality laws of VER-005/VER-007
|
|
4287
|
+
* **and** the inequality laws `y·M ≤ y·M0` (`y ≥ 0, y·C ≤ 0`) and their mixed-sign
|
|
4288
|
+
* kin, which are what an *ordering* argument ("both join slots armed means every
|
|
4289
|
+
* upstream stage has run, so nothing can still halt") looks like in linear
|
|
4290
|
+
* arithmetic — is then available to Spacer as a fact rather than a lemma it has to
|
|
4291
|
+
* invent. On a 50-place agent-dispatch workflow, proper completion under conditional
|
|
4292
|
+
* sinks went from `unknown` after 120 s to `proven` in 1.5 s with this as the only
|
|
4293
|
+
* change; a 53-place pipeline stage before a join, `unknown` at 300 s, proves in
|
|
4294
|
+
* under a second.
|
|
4295
|
+
*
|
|
4296
|
+
* The cost is a larger state (places + transitions) and a slower witness search
|
|
4297
|
+
* on genuinely violated properties (about 1.5× on the nets above), so it is opt-in.
|
|
4298
|
+
* Soundness is unchanged: the counters are exact bookkeeping, the equation holds
|
|
4299
|
+
* on every reachable state by construction (`Strengthening.lean`, the same shape
|
|
4300
|
+
* as the equality laws), and the certificate check re-proves it against the raw
|
|
4301
|
+
* step relation, whose only counter knowledge is the increment. Not applied to the
|
|
4302
|
+
* name-coloured encoding or Route B, which the report says when it applies.
|
|
4303
|
+
*/
|
|
4304
|
+
stateEquation(enabled) {
|
|
4305
|
+
this._stateEquation = enabled;
|
|
4306
|
+
return this;
|
|
4307
|
+
}
|
|
3760
4308
|
/**
|
|
3761
4309
|
* Sets the class-count cap for the ν-aware state-class-graph analysis (NU-050,
|
|
3762
4310
|
* Route B). When the symbolic name-aware graph would exceed this, the analysis
|
|
@@ -3767,6 +4315,29 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3767
4315
|
this._nuMaxClasses = max;
|
|
3768
4316
|
return this;
|
|
3769
4317
|
}
|
|
4318
|
+
/**
|
|
4319
|
+
* Sets the class budget for the bounded state-space enumeration route
|
|
4320
|
+
* (VER-017; default 50 000). `0` disables the route, so every query goes to
|
|
4321
|
+
* the SMT pipeline.
|
|
4322
|
+
*
|
|
4323
|
+
* When the state-class graph closes within the budget the property is decided
|
|
4324
|
+
* exactly — sound and complete over the timed semantics — and no solver runs.
|
|
4325
|
+
* This is what makes a long pipeline tractable: IC3 needs a frame per stage and
|
|
4326
|
+
* its cost climbs with the cube of the length, while enumeration is linear in
|
|
4327
|
+
* the reachable state space. A forty-node chain (370 places, 1 967 classes)
|
|
4328
|
+
* takes 410 s on the fixpoint path and 0.11 s here.
|
|
4329
|
+
*
|
|
4330
|
+
* The route declines when the graph exceeds the budget, and the SMT pipeline
|
|
4331
|
+
* then runs unchanged — it can only add verdicts, never remove them. It is
|
|
4332
|
+
* skipped for ν-nets, which have their own exact route (NU-050, Route B), for
|
|
4333
|
+
* nets with environment places, whose injection the graph does not model, and
|
|
4334
|
+
* for **timed** nets, where its verdict would be the weaker timed claim rather
|
|
4335
|
+
* than the untimed one the encoders make (VER-004).
|
|
4336
|
+
*/
|
|
4337
|
+
enumerationMaxClasses(max) {
|
|
4338
|
+
this._enumerationMaxClasses = max;
|
|
4339
|
+
return this;
|
|
4340
|
+
}
|
|
3770
4341
|
/**
|
|
3771
4342
|
* Selects the ν-net coloured-place fragment for Route B (NU-051). `base`
|
|
3772
4343
|
* (default) admits the shipped mint → matched-join fragment only; `extended`
|
|
@@ -3849,7 +4420,8 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3849
4420
|
this._initialMarking,
|
|
3850
4421
|
this._property,
|
|
3851
4422
|
invariants,
|
|
3852
|
-
this._sinkPlaces
|
|
4423
|
+
this._sinkPlaces,
|
|
4424
|
+
this._conditionalSinks
|
|
3853
4425
|
)
|
|
3854
4426
|
};
|
|
3855
4427
|
}
|
|
@@ -3866,35 +4438,45 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3866
4438
|
requireOutputProducingActions(this.net);
|
|
3867
4439
|
const flatNet = flatten(this.net, this._environmentPlaces, this._environmentMode);
|
|
3868
4440
|
const matrix = IncidenceMatrix.from(flatNet);
|
|
3869
|
-
const { valid: basis } = validateInvariantsExact(
|
|
4441
|
+
const { valid: basis, dropped: basisDropped } = validateInvariantsExact(
|
|
3870
4442
|
matrix,
|
|
3871
4443
|
computePInvariants(matrix, flatNet, this._initialMarking),
|
|
3872
4444
|
flatNet,
|
|
3873
4445
|
this._initialMarking
|
|
3874
4446
|
);
|
|
3875
|
-
const
|
|
4447
|
+
const autoUnion = this._semiflowInvariants === "auto" && basisDropped.some((d) => d.reason.includes("Strengthening.lean H1"));
|
|
4448
|
+
const scriptsHasMatch = [...this.net.transitions].some((t) => t.matchSpec !== null);
|
|
4449
|
+
const { valid: semiflows } = this._semiflowInvariants === true || autoUnion || scriptsHasMatch && this._budgetPlaces.size > 0 ? validateInvariantsExact(
|
|
3876
4450
|
matrix,
|
|
3877
4451
|
computePSemiflows(matrix, flatNet, this._initialMarking),
|
|
3878
4452
|
flatNet,
|
|
3879
4453
|
this._initialMarking
|
|
3880
|
-
);
|
|
4454
|
+
) : { valid: [] };
|
|
3881
4455
|
let invariants = basis;
|
|
3882
|
-
if (this._semiflowInvariants) invariants = strengthenWithSemiflows(basis, semiflows).invariants;
|
|
4456
|
+
if (this._semiflowInvariants === true || autoUnion) invariants = strengthenWithSemiflows(basis, semiflows).invariants;
|
|
3883
4457
|
invariants = canonicalInvariantOrder(invariants);
|
|
3884
4458
|
const attempt = this.colouredAttempt(flatNet, invariants, semiflows);
|
|
4459
|
+
const bound = attempt.plan == null && this._linearBound && !(this._environmentPlaces.size > 0 && this._environmentMode.type === "ignore") ? encodeLinearBound(flatNet, this._initialMarking, this._property) : null;
|
|
3885
4460
|
if (attempt.encoding != null) {
|
|
3886
|
-
return { horn: attempt.encoding.smt2, certificate: null, coloured: true };
|
|
4461
|
+
return { horn: attempt.encoding.smt2, certificate: null, coloured: true, bound };
|
|
3887
4462
|
}
|
|
3888
|
-
const
|
|
4463
|
+
const flat = encodeNet(flatNet, this._initialMarking, this._property, invariants, {
|
|
4464
|
+
sinkPlaces: this._sinkPlaces,
|
|
4465
|
+
produceProofs: this._counterexampleReplay,
|
|
4466
|
+
conditionalSinks: this._conditionalSinks,
|
|
4467
|
+
stateEquation: this._stateEquation
|
|
4468
|
+
});
|
|
3889
4469
|
const certificate = vcScript(
|
|
3890
|
-
placeholderCertificate(flatNet.places.length),
|
|
4470
|
+
placeholderCertificate(flatNet.places.length + flat.counterCount),
|
|
3891
4471
|
flatNet,
|
|
3892
4472
|
this._initialMarking,
|
|
3893
4473
|
this._property,
|
|
3894
4474
|
this._sinkPlaces,
|
|
3895
|
-
invariants
|
|
4475
|
+
invariants,
|
|
4476
|
+
this._conditionalSinks,
|
|
4477
|
+
this._stateEquation
|
|
3896
4478
|
);
|
|
3897
|
-
return { horn, certificate, coloured: false };
|
|
4479
|
+
return { horn: flat.smt2, certificate, coloured: false, bound };
|
|
3898
4480
|
}
|
|
3899
4481
|
/**
|
|
3900
4482
|
* Runs the verification pipeline.
|
|
@@ -3907,10 +4489,34 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3907
4489
|
const report = [];
|
|
3908
4490
|
report.push("=== IC3/PDR SAFETY VERIFICATION ===\n");
|
|
3909
4491
|
report.push(`Net: ${this.net.name}`);
|
|
3910
|
-
const
|
|
4492
|
+
const sinkDesc = describeSinks(this._sinkPlaces, this._conditionalSinks);
|
|
4493
|
+
const propDesc = sinkDesc === null ? propertyDescription(this._property) : `${propertyDescription(this._property)} (${sinkDesc})`;
|
|
3911
4494
|
report.push(`Property: ${propDesc}`);
|
|
3912
4495
|
report.push(`Timeout: ${(this._timeoutMs / 1e3).toFixed(0)}s
|
|
3913
4496
|
`);
|
|
4497
|
+
const absent = unresolvedPropertyPlaceInNet(this.net, this._property);
|
|
4498
|
+
if (absent != null) {
|
|
4499
|
+
const reason = `property names a place that does not resolve in the net ('${absent}'); refusing to certify (the encoding would be vacuously proven)`;
|
|
4500
|
+
report.push("=== RESULT ===\n");
|
|
4501
|
+
report.push(`UNKNOWN: ${reason}`);
|
|
4502
|
+
return buildResult(
|
|
4503
|
+
{ type: "unknown", reason },
|
|
4504
|
+
report.join("\n"),
|
|
4505
|
+
[],
|
|
4506
|
+
[],
|
|
4507
|
+
[],
|
|
4508
|
+
[],
|
|
4509
|
+
performance.now() - start,
|
|
4510
|
+
{
|
|
4511
|
+
places: [...this.net.places].length,
|
|
4512
|
+
transitions: [...this.net.transitions].length,
|
|
4513
|
+
invariantsFound: 0,
|
|
4514
|
+
structuralResult: "n/a (unresolved property place)"
|
|
4515
|
+
},
|
|
4516
|
+
null,
|
|
4517
|
+
"unavailable"
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
3914
4520
|
const hasMatch = [...this.net.transitions].some((t) => t.matchSpec !== null);
|
|
3915
4521
|
const nuBounded = this._budgetPlaces.size > 0;
|
|
3916
4522
|
if (hasMatch && (!isReachabilitySafety(this._property) || !nuBounded)) {
|
|
@@ -3924,7 +4530,8 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3924
4530
|
this._nuMaxClasses,
|
|
3925
4531
|
this._fragmentMode,
|
|
3926
4532
|
this._carrierPlaces,
|
|
3927
|
-
this._prioritySemantics
|
|
4533
|
+
this._prioritySemantics,
|
|
4534
|
+
this._conditionalSinks
|
|
3928
4535
|
);
|
|
3929
4536
|
const deferToRouteA = outcome !== null && outcome.verdict.type === "unknown" && !isReachabilitySafety(this._property) && nuBounded;
|
|
3930
4537
|
if (outcome !== null && !deferToRouteA) {
|
|
@@ -3952,7 +4559,9 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3952
4559
|
transitions: [...this.net.transitions].length,
|
|
3953
4560
|
invariantsFound: 0,
|
|
3954
4561
|
structuralResult: "n/a (\u03BD name-partition SCG)"
|
|
3955
|
-
}
|
|
4562
|
+
},
|
|
4563
|
+
null,
|
|
4564
|
+
"nu-scg"
|
|
3956
4565
|
);
|
|
3957
4566
|
} else if (deferToRouteA) {
|
|
3958
4567
|
report.push(
|
|
@@ -3965,6 +4574,47 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3965
4574
|
);
|
|
3966
4575
|
}
|
|
3967
4576
|
}
|
|
4577
|
+
if (!hasMatch && this._environmentPlaces.size === 0 && this._enumerationMaxClasses > 0 && isUntimed(this.net)) {
|
|
4578
|
+
const enumerated = verifyViaStateClassGraph(
|
|
4579
|
+
this.net,
|
|
4580
|
+
this._initialMarking,
|
|
4581
|
+
this._property,
|
|
4582
|
+
this._sinkPlaces,
|
|
4583
|
+
this._enumerationMaxClasses,
|
|
4584
|
+
this._conditionalSinks
|
|
4585
|
+
);
|
|
4586
|
+
if (enumerated.kind === "decided") {
|
|
4587
|
+
report.push("=== Bounded state-space enumeration (VER-017) ===");
|
|
4588
|
+
report.push(` State classes: ${enumerated.classCount}`);
|
|
4589
|
+
report.push(" P-invariants: not computed (no encoding is built on this route)");
|
|
4590
|
+
report.push(NOTE_ENUMERATED);
|
|
4591
|
+
if (enumerated.transitions.length > 0) {
|
|
4592
|
+
report.push(` Counterexample trace: ${enumerated.trace.length} states, ${enumerated.transitions.length} transitions`);
|
|
4593
|
+
}
|
|
4594
|
+
return buildResult(
|
|
4595
|
+
enumerated.verdict,
|
|
4596
|
+
report.join("\n"),
|
|
4597
|
+
[],
|
|
4598
|
+
[],
|
|
4599
|
+
enumerated.trace,
|
|
4600
|
+
enumerated.transitions,
|
|
4601
|
+
performance.now() - start,
|
|
4602
|
+
{
|
|
4603
|
+
places: [...this.net.places].length,
|
|
4604
|
+
transitions: [...this.net.transitions].length,
|
|
4605
|
+
invariantsFound: 0,
|
|
4606
|
+
structuralResult: "n/a (state-space enumeration)"
|
|
4607
|
+
},
|
|
4608
|
+
// The graph path IS a firing sequence, so a violation is ordered and
|
|
4609
|
+
// confirmed by construction; there is nothing left to replay.
|
|
4610
|
+
enumerated.verdict.type === "violated" ? true : null,
|
|
4611
|
+
"enumeration"
|
|
4612
|
+
);
|
|
4613
|
+
}
|
|
4614
|
+
report.push(
|
|
4615
|
+
`Bounded state-space enumeration truncated at ${this._enumerationMaxClasses} classes (VER-017); verifying via the SMT pipeline.`
|
|
4616
|
+
);
|
|
4617
|
+
}
|
|
3968
4618
|
report.push("Phase 1: Flattening net...");
|
|
3969
4619
|
const flatNet = flatten(this.net, this._environmentPlaces, this._environmentMode);
|
|
3970
4620
|
report.push(` Places: ${flatNet.places.length}`);
|
|
@@ -3989,7 +4639,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3989
4639
|
}
|
|
3990
4640
|
report.push(` Result: ${structResultStr}
|
|
3991
4641
|
`);
|
|
3992
|
-
if (this._property.type === "deadlock-free" && !hasMatch && this._sinkPlaces.size === 0 && structResult.type === "no-potential-deadlock" && this._environmentPlaces.size === 0) {
|
|
4642
|
+
if (this._property.type === "deadlock-free" && !hasMatch && commonerApplies(flatNet) && this._sinkPlaces.size === 0 && this._conditionalSinks.length === 0 && structResult.type === "no-potential-deadlock" && this._environmentPlaces.size === 0) {
|
|
3993
4643
|
report.push("=== RESULT ===\n");
|
|
3994
4644
|
report.push("PROVEN (structural): Deadlock-freedom verified by Commoner's theorem.");
|
|
3995
4645
|
report.push(" All siphons contain initially marked traps.");
|
|
@@ -4002,7 +4652,9 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4002
4652
|
[],
|
|
4003
4653
|
[],
|
|
4004
4654
|
performance.now() - start,
|
|
4005
|
-
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: 0, structuralResult: structResultStr }
|
|
4655
|
+
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: 0, structuralResult: structResultStr },
|
|
4656
|
+
null,
|
|
4657
|
+
"structural"
|
|
4006
4658
|
);
|
|
4007
4659
|
}
|
|
4008
4660
|
report.push("Phase 3: Computing P-invariants...");
|
|
@@ -4013,15 +4665,21 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4013
4665
|
flatNet,
|
|
4014
4666
|
this._initialMarking
|
|
4015
4667
|
);
|
|
4016
|
-
const
|
|
4668
|
+
const basisLostALaw = droppedInvariants.some((d) => d.reason.includes("Strengthening.lean H1"));
|
|
4669
|
+
const semiflowsWanted = this._semiflowInvariants === true || this._semiflowInvariants === "auto" && basisLostALaw || hasMatch && nuBounded;
|
|
4670
|
+
const { valid: semiflows, dropped: droppedSemiflows } = semiflowsWanted ? validateInvariantsExact(
|
|
4017
4671
|
matrix,
|
|
4018
4672
|
computePSemiflows(matrix, flatNet, this._initialMarking),
|
|
4019
4673
|
flatNet,
|
|
4020
4674
|
this._initialMarking
|
|
4021
|
-
);
|
|
4675
|
+
) : { valid: [], dropped: [] };
|
|
4022
4676
|
report.push(` Found: ${basisInvariants.length} P-invariant(s)`);
|
|
4677
|
+
if (this._semiflowInvariants === "auto") {
|
|
4678
|
+
report.push(basisLostALaw ? " Semiflow union: ON (auto \u2014 the basis lost a law to the H1 guard)" : " Semiflow union: off (auto \u2014 the basis is complete, so the semiflows would add no constraint the encoding does not already have; they may still differ in FORM)");
|
|
4679
|
+
}
|
|
4680
|
+
const unionWanted = this._semiflowInvariants === true || this._semiflowInvariants === "auto" && basisLostALaw;
|
|
4023
4681
|
let invariants = basisInvariants;
|
|
4024
|
-
if (
|
|
4682
|
+
if (unionWanted) {
|
|
4025
4683
|
const { invariants: strengthened, added } = strengthenWithSemiflows(basisInvariants, semiflows);
|
|
4026
4684
|
invariants = strengthened;
|
|
4027
4685
|
report.push(` Semiflows encoded as invariants: ${added}`);
|
|
@@ -4045,6 +4703,11 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4045
4703
|
report.push(` Dropped: ${droppedSemiflows.length} semiflow(s) failed the exact re-check`);
|
|
4046
4704
|
}
|
|
4047
4705
|
report.push("");
|
|
4706
|
+
if (!isReachabilitySafety(this._property) && quiescenceUnreachable(flatNet, resolveEnvInjection(flatNet))) {
|
|
4707
|
+
report.push(
|
|
4708
|
+
" NOTE: no marking of this net can be quiescent \u2014 a transition is enabled in every marking (an environment-gated one under modelled injection, VER-006). Every quiescence property is therefore vacuously true here, and a `proven` says nothing about the net."
|
|
4709
|
+
);
|
|
4710
|
+
}
|
|
4048
4711
|
report.push("Phase 4: IC3/PDR verification via Z3 Spacer...");
|
|
4049
4712
|
const stats = {
|
|
4050
4713
|
places: flatNet.places.length,
|
|
@@ -4056,6 +4719,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4056
4719
|
try {
|
|
4057
4720
|
solver = resolveZ3();
|
|
4058
4721
|
} catch (e) {
|
|
4722
|
+
rethrowIfProgrammingError(e);
|
|
4059
4723
|
const reason = e instanceof Z3Unavailable ? e.message : String(e?.message ?? e);
|
|
4060
4724
|
report.push(` Solver: z3 unavailable (${reason})`);
|
|
4061
4725
|
report.push(` Status: UNKNOWN (${reason})
|
|
@@ -4063,11 +4727,35 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4063
4727
|
report.push("=== RESULT ===\n");
|
|
4064
4728
|
report.push(`UNKNOWN: Could not determine ${propDesc}`);
|
|
4065
4729
|
report.push(` Reason: ${reason}`);
|
|
4066
|
-
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
4730
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats, null, "unavailable");
|
|
4067
4731
|
}
|
|
4068
4732
|
report.push(` Solver: z3 ${formatZ3Version(solver.version)}`);
|
|
4069
4733
|
const colouredAttempt = this.colouredAttempt(flatNet, invariants, semiflows);
|
|
4070
4734
|
const colouredPlan = colouredAttempt.plan;
|
|
4735
|
+
if (this._linearBound && colouredPlan == null && isReachabilitySafety(this._property) && !(this._environmentPlaces.size > 0 && this._environmentMode.type === "ignore")) {
|
|
4736
|
+
const proof = await this.linearBoundProof(flatNet, solver, report);
|
|
4737
|
+
if (proof != null) {
|
|
4738
|
+
report.push(" Certificate check: not applicable (structural proof)");
|
|
4739
|
+
report.push("");
|
|
4740
|
+
report.push("=== RESULT ===\n");
|
|
4741
|
+
report.push(`PROVEN (structural): ${propDesc}`);
|
|
4742
|
+
report.push(" Linear state-equation bound: y >= 0 with y.C <= 0 gives y.M <= y.M0 on every");
|
|
4743
|
+
report.push(" reachable marking, and the violating markings exceed it (VER-015).");
|
|
4744
|
+
report.push(` ${proof}`);
|
|
4745
|
+
return this.applyNuGuard(buildResult(
|
|
4746
|
+
{ type: "proven", method: "structural", inductiveInvariant: null },
|
|
4747
|
+
report.join("\n"),
|
|
4748
|
+
invariants,
|
|
4749
|
+
[],
|
|
4750
|
+
[],
|
|
4751
|
+
[],
|
|
4752
|
+
performance.now() - start,
|
|
4753
|
+
stats,
|
|
4754
|
+
null,
|
|
4755
|
+
"structural"
|
|
4756
|
+
), hasMatch, nuBounded, false);
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4071
4759
|
let encoding;
|
|
4072
4760
|
if (colouredPlan != null) {
|
|
4073
4761
|
report.push(
|
|
@@ -4079,7 +4767,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4079
4767
|
report.push(" Status: UNKNOWN (unresolved property place)\n");
|
|
4080
4768
|
report.push("=== RESULT ===\n");
|
|
4081
4769
|
report.push(`UNKNOWN: ${reason}`);
|
|
4082
|
-
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
4770
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats, null, "unavailable");
|
|
4083
4771
|
}
|
|
4084
4772
|
encoding = coloured;
|
|
4085
4773
|
} else {
|
|
@@ -4089,9 +4777,20 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4089
4777
|
report.push(" Status: UNKNOWN (unresolved property place)\n");
|
|
4090
4778
|
report.push("=== RESULT ===\n");
|
|
4091
4779
|
report.push(`UNKNOWN: ${reason}`);
|
|
4092
|
-
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats);
|
|
4780
|
+
return buildResult({ type: "unknown", reason }, report.join("\n"), invariants, [], [], [], performance.now() - start, stats, null, "unavailable");
|
|
4093
4781
|
}
|
|
4094
|
-
encoding =
|
|
4782
|
+
encoding = encodeNet(flatNet, this._initialMarking, this._property, invariants, {
|
|
4783
|
+
sinkPlaces: this._sinkPlaces,
|
|
4784
|
+
produceProofs: this._counterexampleReplay,
|
|
4785
|
+
conditionalSinks: this._conditionalSinks,
|
|
4786
|
+
stateEquation: this._stateEquation
|
|
4787
|
+
});
|
|
4788
|
+
if (this._stateEquation) {
|
|
4789
|
+
report.push(` State equation: encoded over ${encoding.counterCount} firing counters (VER-016)`);
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
if (this._stateEquation && colouredPlan != null) {
|
|
4793
|
+
report.push(" State equation: not applied (name-coloured encoding)");
|
|
4095
4794
|
}
|
|
4096
4795
|
const queryResult = await runZ3Spacer(
|
|
4097
4796
|
solver,
|
|
@@ -4123,7 +4822,9 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4123
4822
|
invariants,
|
|
4124
4823
|
this._sinkPlaces,
|
|
4125
4824
|
solver,
|
|
4126
|
-
this._timeoutMs
|
|
4825
|
+
this._timeoutMs,
|
|
4826
|
+
this._conditionalSinks,
|
|
4827
|
+
this._stateEquation
|
|
4127
4828
|
);
|
|
4128
4829
|
const reason = certificateDowngradeReason(certificate);
|
|
4129
4830
|
if (reason != null) {
|
|
@@ -4167,7 +4868,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4167
4868
|
}
|
|
4168
4869
|
case "violated": {
|
|
4169
4870
|
report.push(" Status: SAT (counterexample found)\n");
|
|
4170
|
-
const decoded = decode(queryResult.answer, flatNet);
|
|
4871
|
+
const decoded = decode(queryResult.answer, flatNet, encoding.counterCount);
|
|
4171
4872
|
if (decoded.note != null) report.push(` Counterexample decoding: ${decoded.note}`);
|
|
4172
4873
|
let confirmed = null;
|
|
4173
4874
|
let trace = [...decoded.states];
|
|
@@ -4179,7 +4880,8 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4179
4880
|
this._initialMarking,
|
|
4180
4881
|
decoded.states,
|
|
4181
4882
|
this._property,
|
|
4182
|
-
this._sinkPlaces
|
|
4883
|
+
this._sinkPlaces,
|
|
4884
|
+
this._conditionalSinks
|
|
4183
4885
|
);
|
|
4184
4886
|
if (assessment.kind === "confirmed") {
|
|
4185
4887
|
confirmed = true;
|
|
@@ -4252,6 +4954,49 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
4252
4954
|
}
|
|
4253
4955
|
}
|
|
4254
4956
|
}
|
|
4957
|
+
/**
|
|
4958
|
+
* Runs the linear state-equation bound query (VER-015) and re-checks its answer in
|
|
4959
|
+
* exact integer arithmetic. Returns the bound as the report prints it when one
|
|
4960
|
+
* separates the violation, `null` otherwise (no bound, solver inconclusive, or a
|
|
4961
|
+
* model that failed the re-check — each named in the report). Never the last word:
|
|
4962
|
+
* `null` hands over to the fixpoint query.
|
|
4963
|
+
*/
|
|
4964
|
+
async linearBoundProof(flatNet, solver, report) {
|
|
4965
|
+
const script2 = encodeLinearBound(flatNet, this._initialMarking, this._property);
|
|
4966
|
+
if (script2 == null) return null;
|
|
4967
|
+
let reply;
|
|
4968
|
+
try {
|
|
4969
|
+
reply = await runZ3Text(solver, script2, "bound", this._timeoutMs, []);
|
|
4970
|
+
} catch (e) {
|
|
4971
|
+
rethrowIfProgrammingError(e);
|
|
4972
|
+
report.push(` Linear state-equation bound: inconclusive (${String(e?.message ?? e)})`);
|
|
4973
|
+
return null;
|
|
4974
|
+
}
|
|
4975
|
+
const stdout = reply.stdout.trim();
|
|
4976
|
+
switch (classifyFirstLine(stdout)) {
|
|
4977
|
+
case "sat": {
|
|
4978
|
+
const y = decodeLinearBound(stdout, flatNet.places.length);
|
|
4979
|
+
const bound = y == null ? null : checkLinearBoundExact(flatNet, this._initialMarking, this._property, y);
|
|
4980
|
+
if (bound == null) {
|
|
4981
|
+
report.push(" Linear state-equation bound: inconclusive (solver model failed the exact re-check)");
|
|
4982
|
+
return null;
|
|
4983
|
+
}
|
|
4984
|
+
const rendered = `${formatLinearBound(flatNet, bound)}; violation needs ${formatLinearDemand(flatNet, this._property, bound)}`;
|
|
4985
|
+
report.push(` Linear state-equation bound: ${rendered}`);
|
|
4986
|
+
report.push(" Status: bound excludes every violating marking (re-checked in exact integer arithmetic)");
|
|
4987
|
+
return rendered;
|
|
4988
|
+
}
|
|
4989
|
+
case "unsat":
|
|
4990
|
+
report.push(" Linear state-equation bound: none separates the violation");
|
|
4991
|
+
return null;
|
|
4992
|
+
case "unknown":
|
|
4993
|
+
report.push(" Linear state-equation bound: inconclusive (Z3 answered unknown)");
|
|
4994
|
+
return null;
|
|
4995
|
+
default:
|
|
4996
|
+
report.push(` Linear state-equation bound: inconclusive (${failureReason(reply, timeoutBudget(this._timeoutMs))})`);
|
|
4997
|
+
return null;
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
4255
5000
|
/**
|
|
4256
5001
|
* ν-net soundness guard (NU-040, NU-050). Applied only when the net contains
|
|
4257
5002
|
* match (ν-join) transitions, and only to a proven/violated verdict (an
|
|
@@ -4306,7 +5051,7 @@ function isReachabilitySafety(property) {
|
|
|
4306
5051
|
return false;
|
|
4307
5052
|
}
|
|
4308
5053
|
}
|
|
4309
|
-
function assessCounterexample(flatNet, initialMarking, decodedStates, property, sinkPlaces) {
|
|
5054
|
+
function assessCounterexample(flatNet, initialMarking, decodedStates, property, sinkPlaces, conditionalSinks = []) {
|
|
4310
5055
|
if (decodedStates.size === 0) {
|
|
4311
5056
|
return {
|
|
4312
5057
|
kind: "unconfirmed",
|
|
@@ -4320,9 +5065,12 @@ function assessCounterexample(flatNet, initialMarking, decodedStates, property,
|
|
|
4320
5065
|
vectorize(initialMarking, flatNet),
|
|
4321
5066
|
[...decodedStates].map((m) => vectorize(m, flatNet)),
|
|
4322
5067
|
property,
|
|
4323
|
-
sinkPlaces
|
|
5068
|
+
sinkPlaces,
|
|
5069
|
+
{},
|
|
5070
|
+
conditionalSinks
|
|
4324
5071
|
);
|
|
4325
5072
|
} catch (e) {
|
|
5073
|
+
rethrowIfProgrammingError(e);
|
|
4326
5074
|
outcome = { kind: "exhausted", reason: `replay threw: ${e?.message ?? e}`, nodesExplored: 0 };
|
|
4327
5075
|
}
|
|
4328
5076
|
switch (outcome.kind) {
|
|
@@ -4373,6 +5121,40 @@ Downgraded to UNKNOWN: ${reason}
|
|
|
4373
5121
|
function truncate(s, max) {
|
|
4374
5122
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (${s.length - max} chars truncated)`;
|
|
4375
5123
|
}
|
|
5124
|
+
function unresolvedPropertyPlaceInNet(net, property) {
|
|
5125
|
+
const declared = /* @__PURE__ */ new Set();
|
|
5126
|
+
for (const p of net.places) declared.add(p.name);
|
|
5127
|
+
for (const place of propertyPlaces(property)) {
|
|
5128
|
+
if (!declared.has(place.name)) return place.name;
|
|
5129
|
+
}
|
|
5130
|
+
return null;
|
|
5131
|
+
}
|
|
5132
|
+
function commonerApplies(flatNet) {
|
|
5133
|
+
for (const ft of flatNet.transitions) {
|
|
5134
|
+
if (ft.readPlaces.length > 0 || ft.inhibitorPlaces.length > 0 || ft.resetPlaces.length > 0) return false;
|
|
5135
|
+
if (ft.consumeAll.some(Boolean)) return false;
|
|
5136
|
+
if (ft.preVector.some((w) => w > 1)) return false;
|
|
5137
|
+
}
|
|
5138
|
+
return true;
|
|
5139
|
+
}
|
|
5140
|
+
function propertyPlaces(property) {
|
|
5141
|
+
switch (property.type) {
|
|
5142
|
+
case "deadlock-free":
|
|
5143
|
+
return [];
|
|
5144
|
+
case "terminates-at-sink":
|
|
5145
|
+
return [];
|
|
5146
|
+
case "mutual-exclusion":
|
|
5147
|
+
return [property.p1, property.p2];
|
|
5148
|
+
case "place-bound":
|
|
5149
|
+
return [property.place];
|
|
5150
|
+
case "branch-place-bound":
|
|
5151
|
+
return [property.place];
|
|
5152
|
+
case "unreachable":
|
|
5153
|
+
return [...property.places];
|
|
5154
|
+
case "joined-or-dead-lettered":
|
|
5155
|
+
return [property.pending];
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
4376
5158
|
function unresolvedPropertyPlace(flatNet, property) {
|
|
4377
5159
|
const named = (() => {
|
|
4378
5160
|
switch (property.type) {
|
|
@@ -4408,8 +5190,8 @@ function formatInvariant(inv, flatNet) {
|
|
|
4408
5190
|
}
|
|
4409
5191
|
return `${parts.length === 0 ? "0" : parts.join(" + ")} = ${inv.constant}`;
|
|
4410
5192
|
}
|
|
4411
|
-
function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics, counterexampleConfirmed = null) {
|
|
4412
|
-
return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, counterexampleConfirmed, elapsedMs, statistics };
|
|
5193
|
+
function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics, counterexampleConfirmed = null, route = "smt") {
|
|
5194
|
+
return { verdict, route, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, counterexampleConfirmed, elapsedMs, statistics };
|
|
4413
5195
|
}
|
|
4414
5196
|
|
|
4415
5197
|
// src/verification/smt-verification-result.ts
|
|
@@ -4445,6 +5227,7 @@ export {
|
|
|
4445
5227
|
transformAsync,
|
|
4446
5228
|
produce,
|
|
4447
5229
|
withTimeout,
|
|
5230
|
+
rethrowIfProgrammingError,
|
|
4448
5231
|
MarkingState,
|
|
4449
5232
|
MarkingStateBuilder,
|
|
4450
5233
|
deadlockFree,
|
|
@@ -4455,6 +5238,9 @@ export {
|
|
|
4455
5238
|
branchPlaceBound,
|
|
4456
5239
|
joinedOrDeadLettered,
|
|
4457
5240
|
propertyDescription,
|
|
5241
|
+
strandingExcuses,
|
|
5242
|
+
strandsToken,
|
|
5243
|
+
describeSinks,
|
|
4458
5244
|
flatTransition,
|
|
4459
5245
|
alwaysAvailable,
|
|
4460
5246
|
bounded,
|
|
@@ -4484,13 +5270,24 @@ export {
|
|
|
4484
5270
|
runZ3Text,
|
|
4485
5271
|
runZ3Spacer,
|
|
4486
5272
|
encode,
|
|
5273
|
+
encodeNet,
|
|
4487
5274
|
encodeStepRelationSmt2,
|
|
4488
5275
|
checkCertificate,
|
|
4489
5276
|
vcScript,
|
|
5277
|
+
violationDemand,
|
|
5278
|
+
encodeLinearBound,
|
|
5279
|
+
decodeLinearBound,
|
|
5280
|
+
checkLinearBoundExact,
|
|
5281
|
+
formatLinearBound,
|
|
5282
|
+
formatLinearDemand,
|
|
5283
|
+
decideOverClasses,
|
|
4490
5284
|
DBM,
|
|
4491
5285
|
StateClass,
|
|
4492
5286
|
requireOutputProducingActions,
|
|
4493
5287
|
StateClassGraph,
|
|
5288
|
+
isUntimed,
|
|
5289
|
+
NOTE_ENUMERATED,
|
|
5290
|
+
verifyViaStateClassGraph,
|
|
4494
5291
|
decode,
|
|
4495
5292
|
decodeStateSet,
|
|
4496
5293
|
flatNetPlaceCount,
|
|
@@ -4502,4 +5299,4 @@ export {
|
|
|
4502
5299
|
isProven,
|
|
4503
5300
|
isViolated
|
|
4504
5301
|
};
|
|
4505
|
-
//# sourceMappingURL=chunk-
|
|
5302
|
+
//# sourceMappingURL=chunk-EL4E6LVO.js.map
|