libpetri 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -1
- package/dist/chunk-6NH64RCU.js +1016 -0
- package/dist/chunk-6NH64RCU.js.map +1 -0
- package/dist/{chunk-5W6SVYPD.js → chunk-JZIEWVAV.js} +839 -44
- package/dist/chunk-JZIEWVAV.js.map +1 -0
- package/dist/debug/index.d.ts +2 -2
- package/dist/doclet/index.d.ts +12 -3
- package/dist/doclet/index.js +5 -1
- package/dist/doclet/index.js.map +1 -1
- package/dist/doclet/resources/petrinet-diagrams.css +21 -0
- package/dist/doclet/resources/petrinet-diagrams.js +3575 -3573
- package/dist/{render-ZGZEZ5RK.js → elk-place-YVNQFGXI.js} +3 -258
- package/dist/elk-place-YVNQFGXI.js.map +1 -0
- package/dist/{event-store-Df_sAVQ_.d.ts → event-store-BFX_yJ8I.d.ts} +1 -1
- package/dist/export/index.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/pan-zoom-Cp51IkDl.d.ts +33 -0
- package/dist/{petri-net-UQBBkvLl.d.ts → petri-net-WSScMyDL.d.ts} +20 -1
- package/dist/preprocess-FN3F75JR.js +193 -0
- package/dist/preprocess-FN3F75JR.js.map +1 -0
- package/dist/render-QOHGDWNE.js +78 -0
- package/dist/render-QOHGDWNE.js.map +1 -0
- package/dist/render-dom/index.d.ts +28 -29
- package/dist/render-dom/index.js +14 -21
- package/dist/render-dom/index.js.map +1 -1
- package/dist/verification/index.d.ts +269 -4
- package/dist/verification/index.js +7 -1
- package/dist/verification/index.js.map +1 -1
- package/dist/viewer/index.d.ts +24 -32
- package/dist/viewer/index.js +9 -1003
- package/dist/viewer/index.js.map +1 -1
- package/dist/viewer/viewer.css +21 -0
- package/dist/viewer/viewer.iife.js +3575 -3573
- package/package.json +2 -2
- package/dist/chunk-5W6SVYPD.js.map +0 -1
- package/dist/render-ZGZEZ5RK.js.map +0 -1
|
@@ -661,6 +661,10 @@ function computePInvariants(matrix, flatNet, initialMarking) {
|
|
|
661
661
|
}
|
|
662
662
|
}
|
|
663
663
|
if (!isZero) continue;
|
|
664
|
+
if (!rowIsExact(augmented[row], T, P)) {
|
|
665
|
+
invariants.push(rawInvariant(augmented[row], T, P, flatNet, initialMarking));
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
664
668
|
const weights = new Array(P);
|
|
665
669
|
let allNonNegative = true;
|
|
666
670
|
let hasPositive = false;
|
|
@@ -711,6 +715,105 @@ function computePInvariants(matrix, flatNet, initialMarking) {
|
|
|
711
715
|
}
|
|
712
716
|
return invariants;
|
|
713
717
|
}
|
|
718
|
+
function rowIsExact(row, T, P) {
|
|
719
|
+
for (let i = 0; i < P; i++) {
|
|
720
|
+
if (!Number.isSafeInteger(row[T + i])) return false;
|
|
721
|
+
}
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
function rawInvariant(row, T, P, flatNet, initialMarking) {
|
|
725
|
+
const weights = new Array(P);
|
|
726
|
+
const support = /* @__PURE__ */ new Set();
|
|
727
|
+
let constant = 0;
|
|
728
|
+
for (let i = 0; i < P; i++) {
|
|
729
|
+
weights[i] = row[T + i];
|
|
730
|
+
if (weights[i] !== 0) {
|
|
731
|
+
support.add(i);
|
|
732
|
+
constant += weights[i] * initialMarking.tokens(flatNet.places[i]);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return pInvariant(weights, constant, support);
|
|
736
|
+
}
|
|
737
|
+
function validateInvariantsExact(matrix, invariants, flatNet, initialMarking) {
|
|
738
|
+
const nonlinear = nonlinearPlaces(flatNet);
|
|
739
|
+
const valid = [];
|
|
740
|
+
const dropped = [];
|
|
741
|
+
for (const inv of invariants) {
|
|
742
|
+
const reason = exactCheckFailure(matrix, inv, nonlinear, flatNet, initialMarking);
|
|
743
|
+
if (reason === null) {
|
|
744
|
+
valid.push(inv);
|
|
745
|
+
} else {
|
|
746
|
+
dropped.push({ invariant: inv, reason });
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return { valid, dropped };
|
|
750
|
+
}
|
|
751
|
+
function nonlinearPlaces(flatNet) {
|
|
752
|
+
const nonlinear = /* @__PURE__ */ new Set();
|
|
753
|
+
for (const ft of flatNet.transitions) {
|
|
754
|
+
for (let p = 0; p < ft.consumeAll.length; p++) {
|
|
755
|
+
if (ft.consumeAll[p]) nonlinear.add(p);
|
|
756
|
+
}
|
|
757
|
+
for (const p of ft.resetPlaces) nonlinear.add(p);
|
|
758
|
+
}
|
|
759
|
+
return nonlinear;
|
|
760
|
+
}
|
|
761
|
+
function exactCheckFailure(matrix, inv, nonlinear, flatNet, initialMarking) {
|
|
762
|
+
const P = matrix.numPlaces();
|
|
763
|
+
const T = matrix.numTransitions();
|
|
764
|
+
if (inv.weights.length !== P) {
|
|
765
|
+
return `weight vector has ${inv.weights.length} entries, expected ${P}`;
|
|
766
|
+
}
|
|
767
|
+
for (let p = 0; p < P; p++) {
|
|
768
|
+
if (!Number.isSafeInteger(inv.weights[p])) {
|
|
769
|
+
return `weight overflow at place '${placeName(flatNet, p)}' (exact value outside this implementation's integer extraction range)`;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (!Number.isSafeInteger(inv.constant)) {
|
|
773
|
+
return `constant ${inv.constant} is outside the safe-integer range`;
|
|
774
|
+
}
|
|
775
|
+
for (let p = 0; p < inv.weights.length; p++) {
|
|
776
|
+
if (inv.weights[p] !== 0 && nonlinear.has(p)) {
|
|
777
|
+
return `support intersects consume-all/reset place '${placeName(flatNet, p)}' (non-linear consumption; see Strengthening.lean H1)`;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
const y = inv.weights.map((w) => BigInt(w));
|
|
781
|
+
const incidence = matrix.incidence();
|
|
782
|
+
for (let t = 0; t < T; t++) {
|
|
783
|
+
const row = incidence[t];
|
|
784
|
+
let dot = 0n;
|
|
785
|
+
for (let p = 0; p < P; p++) {
|
|
786
|
+
if (y[p] === 0n) continue;
|
|
787
|
+
if (!Number.isSafeInteger(row[p])) {
|
|
788
|
+
return `incidence entry ${row[p]} at [t=${t}][p=${p}] is outside the safe-integer range`;
|
|
789
|
+
}
|
|
790
|
+
dot += y[p] * BigInt(row[p]);
|
|
791
|
+
}
|
|
792
|
+
if (dot !== 0n) {
|
|
793
|
+
return `y*C is ${dot} (not 0) at ${columnName(flatNet, t)}`;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
let exact = 0n;
|
|
797
|
+
for (let p = 0; p < P; p++) {
|
|
798
|
+
if (y[p] === 0n) continue;
|
|
799
|
+
const tokens = initialMarking.tokens(flatNet.places[p]);
|
|
800
|
+
if (!Number.isSafeInteger(tokens)) {
|
|
801
|
+
return `initial marking of place ${p} (${tokens}) is outside the safe-integer range`;
|
|
802
|
+
}
|
|
803
|
+
exact += y[p] * BigInt(tokens);
|
|
804
|
+
}
|
|
805
|
+
if (exact !== BigInt(inv.constant)) {
|
|
806
|
+
return `constant ${inv.constant} does not match exact y*M0 = ${exact}`;
|
|
807
|
+
}
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
function placeName(flatNet, p) {
|
|
811
|
+
return flatNet.places[p]?.name ?? `#${p}`;
|
|
812
|
+
}
|
|
813
|
+
function columnName(flatNet, t) {
|
|
814
|
+
const ft = flatNet.transitions[t];
|
|
815
|
+
return ft != null ? `transition '${ft.name}'` : `env-injector column ${t - flatNet.transitions.length}`;
|
|
816
|
+
}
|
|
714
817
|
function computePSemiflows(matrix, flatNet, initialMarking) {
|
|
715
818
|
const np = matrix.numPlaces();
|
|
716
819
|
const nt = matrix.numTransitions();
|
|
@@ -1002,11 +1105,13 @@ async function createSpacerRunner(timeoutMs) {
|
|
|
1002
1105
|
const status = await fp.query(errorExpr);
|
|
1003
1106
|
if (status === "unsat") {
|
|
1004
1107
|
let invariantFormula = null;
|
|
1108
|
+
let provenAnswer = null;
|
|
1005
1109
|
const levelInvariants = [];
|
|
1006
1110
|
try {
|
|
1007
1111
|
const answer = fp.getAnswer();
|
|
1008
1112
|
if (answer != null) {
|
|
1009
1113
|
invariantFormula = answer.toString();
|
|
1114
|
+
provenAnswer = answer;
|
|
1010
1115
|
}
|
|
1011
1116
|
} catch {
|
|
1012
1117
|
}
|
|
@@ -1022,7 +1127,7 @@ async function createSpacerRunner(timeoutMs) {
|
|
|
1022
1127
|
} catch {
|
|
1023
1128
|
}
|
|
1024
1129
|
}
|
|
1025
|
-
return { type: "proven", invariantFormula, levelInvariants };
|
|
1130
|
+
return { type: "proven", invariantFormula, levelInvariants, answer: provenAnswer };
|
|
1026
1131
|
}
|
|
1027
1132
|
if (status === "sat") {
|
|
1028
1133
|
let answer = null;
|
|
@@ -1107,18 +1212,9 @@ function encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P) {
|
|
|
1107
1212
|
const reachBody = reachable.call(...mVars);
|
|
1108
1213
|
const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
|
|
1109
1214
|
const fireRelation = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
|
|
1110
|
-
|
|
1111
|
-
for (let i = 0; i < P; i++) {
|
|
1112
|
-
nonNeg = ctx.And(nonNeg, mPrimeVars[i].ge(0));
|
|
1113
|
-
}
|
|
1215
|
+
const nonNeg = encodeNonNegativity(ctx, mPrimeVars, P);
|
|
1114
1216
|
const invConstraints = encodeInvariantConstraints(ctx, invariants, mPrimeVars, P);
|
|
1115
|
-
|
|
1116
|
-
for (const [name, bound] of flatNet.environmentBounds) {
|
|
1117
|
-
const idx = flatNet.placeIndex.get(name);
|
|
1118
|
-
if (idx != null) {
|
|
1119
|
-
envBounds = ctx.And(envBounds, mPrimeVars[idx].le(bound));
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1217
|
+
const envBounds = encodeEnvBounds(ctx, flatNet, mPrimeVars);
|
|
1122
1218
|
const body = ctx.And(reachBody, enabled, fireRelation, nonNeg, invConstraints, envBounds);
|
|
1123
1219
|
const head = reachable.call(...mPrimeVars);
|
|
1124
1220
|
const allVars = [...mVars, ...mPrimeVars];
|
|
@@ -1135,6 +1231,31 @@ function encodeInjectionRule(ctx, fp, reachable, idx, bound, P) {
|
|
|
1135
1231
|
mPrimeVars.push(Int.const(`mp${i}`));
|
|
1136
1232
|
}
|
|
1137
1233
|
const reachBody = reachable.call(...mVars);
|
|
1234
|
+
const fire = encodeInjectionFire(ctx, idx, mVars, mPrimeVars, P);
|
|
1235
|
+
const guard = encodeInjectionGuard(ctx, idx, bound, mVars);
|
|
1236
|
+
const body = ctx.And(reachBody, guard, fire);
|
|
1237
|
+
const head = reachable.call(...mPrimeVars);
|
|
1238
|
+
const qRule = ctx.ForAll([...mVars, ...mPrimeVars], ctx.Implies(body, head));
|
|
1239
|
+
fp.addRule(qRule, `env_inject_${idx}`);
|
|
1240
|
+
}
|
|
1241
|
+
function encodeNonNegativity(ctx, vars, P) {
|
|
1242
|
+
let result = ctx.Bool.val(true);
|
|
1243
|
+
for (let i = 0; i < P; i++) {
|
|
1244
|
+
result = ctx.And(result, vars[i].ge(0));
|
|
1245
|
+
}
|
|
1246
|
+
return result;
|
|
1247
|
+
}
|
|
1248
|
+
function encodeEnvBounds(ctx, flatNet, vars) {
|
|
1249
|
+
let result = ctx.Bool.val(true);
|
|
1250
|
+
for (const [name, bound] of flatNet.environmentBounds) {
|
|
1251
|
+
const idx = flatNet.placeIndex.get(name);
|
|
1252
|
+
if (idx != null) {
|
|
1253
|
+
result = ctx.And(result, vars[idx].le(bound));
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return result;
|
|
1257
|
+
}
|
|
1258
|
+
function encodeInjectionFire(ctx, idx, mVars, mPrimeVars, P) {
|
|
1138
1259
|
let fire = ctx.Bool.val(true);
|
|
1139
1260
|
for (let i = 0; i < P; i++) {
|
|
1140
1261
|
if (i === idx) {
|
|
@@ -1143,11 +1264,31 @@ function encodeInjectionRule(ctx, fp, reachable, idx, bound, P) {
|
|
|
1143
1264
|
fire = ctx.And(fire, mPrimeVars[i].eq(mVars[i]));
|
|
1144
1265
|
}
|
|
1145
1266
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1267
|
+
return fire;
|
|
1268
|
+
}
|
|
1269
|
+
function encodeInjectionGuard(ctx, idx, bound, mVars) {
|
|
1270
|
+
return bound === null ? ctx.Bool.val(true) : mVars[idx].lt(bound);
|
|
1271
|
+
}
|
|
1272
|
+
function encodeStepRelation(ctx, flatNet, mVars, mPrimeVars) {
|
|
1273
|
+
const P = flatNet.places.length;
|
|
1274
|
+
const disjuncts = [];
|
|
1275
|
+
for (const ft of flatNet.transitions) {
|
|
1276
|
+
const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
|
|
1277
|
+
const fire = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
|
|
1278
|
+
const nonNeg = encodeNonNegativity(ctx, mPrimeVars, P);
|
|
1279
|
+
const envBounds = encodeEnvBounds(ctx, flatNet, mPrimeVars);
|
|
1280
|
+
disjuncts.push(ctx.And(enabled, fire, nonNeg, envBounds));
|
|
1281
|
+
}
|
|
1282
|
+
for (const [name, bound] of flatNet.environmentInjection) {
|
|
1283
|
+
const idx = flatNet.placeIndex.get(name);
|
|
1284
|
+
if (idx == null) continue;
|
|
1285
|
+
disjuncts.push(ctx.And(
|
|
1286
|
+
encodeInjectionGuard(ctx, idx, bound, mVars),
|
|
1287
|
+
encodeInjectionFire(ctx, idx, mVars, mPrimeVars, P)
|
|
1288
|
+
));
|
|
1289
|
+
}
|
|
1290
|
+
if (disjuncts.length === 0) return ctx.Bool.val(false);
|
|
1291
|
+
return ctx.Or(...disjuncts);
|
|
1151
1292
|
}
|
|
1152
1293
|
function injectedEnvIndices(flatNet) {
|
|
1153
1294
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -1298,6 +1439,197 @@ function encodeInvariantConstraints(ctx, invariants, mVars, P) {
|
|
|
1298
1439
|
return result;
|
|
1299
1440
|
}
|
|
1300
1441
|
|
|
1442
|
+
// src/verification/z3/certificate-checker.ts
|
|
1443
|
+
async function checkCertificate(ctx, answer, flatNet, initialMarking, property, invariants, sinkPlaces, timeoutMs) {
|
|
1444
|
+
try {
|
|
1445
|
+
if (answer == null) {
|
|
1446
|
+
return {
|
|
1447
|
+
type: "unavailable",
|
|
1448
|
+
reason: "invariant missing (Z3 produced no inductive-invariant answer)",
|
|
1449
|
+
invariant: null
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
const P = flatNet.places.length;
|
|
1453
|
+
const Int = ctx.Int;
|
|
1454
|
+
const mVars = [];
|
|
1455
|
+
const mPrimeVars = [];
|
|
1456
|
+
for (let i = 0; i < P; i++) {
|
|
1457
|
+
mVars.push(Int.const(`m${i}`));
|
|
1458
|
+
mPrimeVars.push(Int.const(`mp${i}`));
|
|
1459
|
+
}
|
|
1460
|
+
const invariant = extractInvariant(ctx, answer, P, mVars);
|
|
1461
|
+
if (invariant == null) {
|
|
1462
|
+
return {
|
|
1463
|
+
type: "unavailable",
|
|
1464
|
+
reason: "invariant unparseable (no Reachable definition recognized in the Z3 answer)",
|
|
1465
|
+
invariant: String(answer)
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
const candidate = invariants.length > 0 ? ctx.And(invariant, encodeInvariantConstraints(ctx, invariants, mVars, P)) : invariant;
|
|
1469
|
+
const failure = await validateCandidate(
|
|
1470
|
+
ctx,
|
|
1471
|
+
candidate,
|
|
1472
|
+
flatNet,
|
|
1473
|
+
initialMarking,
|
|
1474
|
+
property,
|
|
1475
|
+
sinkPlaces,
|
|
1476
|
+
timeoutMs,
|
|
1477
|
+
mVars,
|
|
1478
|
+
mPrimeVars
|
|
1479
|
+
);
|
|
1480
|
+
if (failure == null) {
|
|
1481
|
+
return { type: "passed", invariant: String(candidate) };
|
|
1482
|
+
}
|
|
1483
|
+
return { type: "failed", vc: failure.vc, detail: failure.detail, invariant: String(candidate) };
|
|
1484
|
+
} catch (e) {
|
|
1485
|
+
return {
|
|
1486
|
+
type: "unavailable",
|
|
1487
|
+
reason: `certificate check error: ${e?.message ?? e}`,
|
|
1488
|
+
invariant: null
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
async function validateCandidate(ctx, candidate, flatNet, initialMarking, property, sinkPlaces, timeoutMs, mVars, mPrimeVars) {
|
|
1493
|
+
const P = flatNet.places.length;
|
|
1494
|
+
const Int = ctx.Int;
|
|
1495
|
+
let nonNegM = ctx.Bool.val(true);
|
|
1496
|
+
for (let i = 0; i < P; i++) {
|
|
1497
|
+
nonNegM = ctx.And(nonNegM, mVars[i].ge(0));
|
|
1498
|
+
}
|
|
1499
|
+
const initPairs = mVars.map((v, i) => [
|
|
1500
|
+
v,
|
|
1501
|
+
Int.val(initialMarking.tokens(flatNet.places[i]))
|
|
1502
|
+
]);
|
|
1503
|
+
const candidateAtInit = ctx.substitute(candidate, ...initPairs);
|
|
1504
|
+
const vc1 = await checkUnsat(ctx, flatNet, mVars, timeoutMs, "initiation (VC1)", [ctx.Not(candidateAtInit)]);
|
|
1505
|
+
if (vc1 != null) return vc1;
|
|
1506
|
+
const primePairs = mVars.map((v, i) => [v, mPrimeVars[i]]);
|
|
1507
|
+
const candidatePrime = ctx.substitute(candidate, ...primePairs);
|
|
1508
|
+
const step = encodeStepRelation(ctx, flatNet, mVars, mPrimeVars);
|
|
1509
|
+
const vc2 = await checkUnsat(
|
|
1510
|
+
ctx,
|
|
1511
|
+
flatNet,
|
|
1512
|
+
mVars,
|
|
1513
|
+
timeoutMs,
|
|
1514
|
+
"consecution (VC2)",
|
|
1515
|
+
[candidate, nonNegM, step, ctx.Not(candidatePrime)]
|
|
1516
|
+
);
|
|
1517
|
+
if (vc2 != null) return vc2;
|
|
1518
|
+
const bad = encodePropertyViolation(ctx, flatNet, property, sinkPlaces, mVars, P);
|
|
1519
|
+
const vc3 = await checkUnsat(ctx, flatNet, mVars, timeoutMs, "safety (VC3)", [candidate, nonNegM, bad]);
|
|
1520
|
+
if (vc3 != null) return vc3;
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
function extractInvariant(ctx, answer, P, mVars) {
|
|
1524
|
+
for (const conjunct of topLevelConjuncts(ctx, answer)) {
|
|
1525
|
+
const invariant = tryExtractDefinition(ctx, conjunct, P, mVars);
|
|
1526
|
+
if (invariant != null) return invariant;
|
|
1527
|
+
}
|
|
1528
|
+
return null;
|
|
1529
|
+
}
|
|
1530
|
+
function topLevelConjuncts(ctx, expr) {
|
|
1531
|
+
if (!ctx.isQuantifier(expr) && ctx.isApp(expr) && String(expr.decl().name()) === "and") {
|
|
1532
|
+
const out = [];
|
|
1533
|
+
const n = expr.numArgs();
|
|
1534
|
+
for (let i = 0; i < n; i++) out.push(expr.arg(i));
|
|
1535
|
+
return out;
|
|
1536
|
+
}
|
|
1537
|
+
return [expr];
|
|
1538
|
+
}
|
|
1539
|
+
function tryExtractDefinition(ctx, expr, P, mVars) {
|
|
1540
|
+
if (ctx.isQuantifier(expr) && expr.is_forall()) {
|
|
1541
|
+
const q = expr;
|
|
1542
|
+
const parts2 = splitDefinition(ctx, q.body(), P);
|
|
1543
|
+
if (parts2 == null) return null;
|
|
1544
|
+
const numVars = q.num_vars();
|
|
1545
|
+
const to = new Array(numVars);
|
|
1546
|
+
for (let j = 0; j < P; j++) {
|
|
1547
|
+
const arg = parts2.app.arg(j);
|
|
1548
|
+
if (!ctx.isVar(arg)) return null;
|
|
1549
|
+
const idx = ctx.getVarIndex(arg);
|
|
1550
|
+
if (idx >= numVars || to[idx] != null) return null;
|
|
1551
|
+
to[idx] = mVars[j];
|
|
1552
|
+
}
|
|
1553
|
+
for (let i = 0; i < numVars; i++) {
|
|
1554
|
+
if (to[i] == null) to[i] = ctx.Int.const(`certFree${i}`);
|
|
1555
|
+
}
|
|
1556
|
+
return ctx.substituteVars(parts2.phi, ...to);
|
|
1557
|
+
}
|
|
1558
|
+
const parts = splitDefinition(ctx, expr, P);
|
|
1559
|
+
if (parts == null) return null;
|
|
1560
|
+
const pairs = [];
|
|
1561
|
+
for (let j = 0; j < P; j++) {
|
|
1562
|
+
const arg = parts.app.arg(j);
|
|
1563
|
+
if (ctx.isVar(arg)) return null;
|
|
1564
|
+
pairs.push([arg, mVars[j]]);
|
|
1565
|
+
}
|
|
1566
|
+
if (pairs.length === 0) return parts.phi;
|
|
1567
|
+
return ctx.substitute(parts.phi, ...pairs);
|
|
1568
|
+
}
|
|
1569
|
+
function splitDefinition(ctx, body, P) {
|
|
1570
|
+
if (ctx.isQuantifier(body) || !ctx.isApp(body)) return null;
|
|
1571
|
+
const b = body;
|
|
1572
|
+
const decl = String(b.decl().name());
|
|
1573
|
+
if ((decl === "=" || decl === "iff") && b.numArgs() === 2) {
|
|
1574
|
+
if (isReachableApp(ctx, b.arg(0), P)) return { app: b.arg(0), phi: b.arg(1) };
|
|
1575
|
+
if (isReachableApp(ctx, b.arg(1), P)) return { app: b.arg(1), phi: b.arg(0) };
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
if (decl === "=>" && b.numArgs() === 2 && isReachableApp(ctx, b.arg(0), P)) {
|
|
1579
|
+
return { app: b.arg(0), phi: b.arg(1) };
|
|
1580
|
+
}
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
function isReachableApp(ctx, expr, P) {
|
|
1584
|
+
return ctx.isApp(expr) && String(expr.decl().name()) === "Reachable" && expr.numArgs() === P;
|
|
1585
|
+
}
|
|
1586
|
+
async function checkUnsat(ctx, flatNet, mVars, timeoutMs, vc, assertions) {
|
|
1587
|
+
const solver = new ctx.Solver();
|
|
1588
|
+
if (timeoutMs > 0) {
|
|
1589
|
+
solver.set("timeout", Math.min(timeoutMs, 2147483647));
|
|
1590
|
+
}
|
|
1591
|
+
for (const assertion of assertions) {
|
|
1592
|
+
solver.add(assertion);
|
|
1593
|
+
}
|
|
1594
|
+
const status = await solver.check();
|
|
1595
|
+
if (status === "unsat") return null;
|
|
1596
|
+
if (status === "sat") {
|
|
1597
|
+
const witness = describeWitness(solver, flatNet, mVars);
|
|
1598
|
+
return {
|
|
1599
|
+
vc,
|
|
1600
|
+
detail: `solver returned SATISFIABLE${witness == null ? "" : ` (witness: ${witness})`}`
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
let why = null;
|
|
1604
|
+
try {
|
|
1605
|
+
why = String(solver.reasonUnknown());
|
|
1606
|
+
} catch {
|
|
1607
|
+
}
|
|
1608
|
+
return { vc, detail: `solver returned UNKNOWN${why == null || why === "" ? "" : ` (${why})`}` };
|
|
1609
|
+
}
|
|
1610
|
+
function describeWitness(solver, flatNet, mVars) {
|
|
1611
|
+
try {
|
|
1612
|
+
const model = solver.model();
|
|
1613
|
+
const parts = [];
|
|
1614
|
+
let length = 0;
|
|
1615
|
+
for (let i = 0; i < mVars.length; i++) {
|
|
1616
|
+
const value = model.eval(mVars[i], false);
|
|
1617
|
+
const text = value == null ? "" : String(value);
|
|
1618
|
+
if (!/^(-?\d+|\(- \d+\))$/.test(text)) continue;
|
|
1619
|
+
const part = `${flatNet.places[i].name}=${text}`;
|
|
1620
|
+
parts.push(part);
|
|
1621
|
+
length += part.length + 2;
|
|
1622
|
+
if (length > 160) {
|
|
1623
|
+
parts.push("...");
|
|
1624
|
+
break;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return parts.length === 0 ? null : parts.join(", ");
|
|
1628
|
+
} catch {
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1301
1633
|
// src/verification/analysis/dbm.ts
|
|
1302
1634
|
var EPSILON = 1e-9;
|
|
1303
1635
|
var DBM = class _DBM {
|
|
@@ -1831,19 +2163,39 @@ function consumeFromPlace(builder, place, count, environmentPlaces, environmentM
|
|
|
1831
2163
|
}
|
|
1832
2164
|
|
|
1833
2165
|
// src/verification/z3/counterexample-decoder.ts
|
|
2166
|
+
function describeDecodeFailure(failure) {
|
|
2167
|
+
if (failure == null) {
|
|
2168
|
+
return "no Reachable applications with concrete arguments found in the derivation";
|
|
2169
|
+
}
|
|
2170
|
+
switch (failure.kind) {
|
|
2171
|
+
case "no-answer":
|
|
2172
|
+
return "Z3 produced no derivation answer";
|
|
2173
|
+
case "traversal-error":
|
|
2174
|
+
return `derivation walk failed: ${failure.message}`;
|
|
2175
|
+
case "non-concrete":
|
|
2176
|
+
return `${failure.skipped} Reachable application(s) had non-concrete arguments`;
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
1834
2179
|
function decode(ctx, answer, flatNet) {
|
|
1835
2180
|
const trace = [];
|
|
1836
2181
|
const transitions = [];
|
|
2182
|
+
const stateByKey = /* @__PURE__ */ new Map();
|
|
1837
2183
|
if (answer == null) {
|
|
1838
|
-
return { trace, transitions };
|
|
2184
|
+
return { trace, transitions, states: /* @__PURE__ */ new Set(), failure: { kind: "no-answer" } };
|
|
1839
2185
|
}
|
|
2186
|
+
const counters = { skipped: 0 };
|
|
2187
|
+
let failure = null;
|
|
1840
2188
|
try {
|
|
1841
|
-
extractTrace(ctx, answer, flatNet, trace, transitions);
|
|
1842
|
-
} catch {
|
|
2189
|
+
extractTrace(ctx, answer, flatNet, trace, transitions, stateByKey, counters);
|
|
2190
|
+
} catch (e) {
|
|
2191
|
+
failure = { kind: "traversal-error", message: String(e?.message ?? e) };
|
|
1843
2192
|
}
|
|
1844
|
-
|
|
2193
|
+
if (failure == null && counters.skipped > 0) {
|
|
2194
|
+
failure = { kind: "non-concrete", skipped: counters.skipped };
|
|
2195
|
+
}
|
|
2196
|
+
return { trace, transitions, states: new Set(stateByKey.values()), failure };
|
|
1845
2197
|
}
|
|
1846
|
-
function extractTrace(ctx, expr, flatNet, trace, transitions) {
|
|
2198
|
+
function extractTrace(ctx, expr, flatNet, trace, transitions, stateByKey, counters) {
|
|
1847
2199
|
if (expr == null) return;
|
|
1848
2200
|
if (!ctx.isApp(expr)) return;
|
|
1849
2201
|
let name;
|
|
@@ -1860,6 +2212,10 @@ function extractTrace(ctx, expr, flatNet, trace, transitions) {
|
|
|
1860
2212
|
const marking = extractMarking(ctx, expr, flatNet);
|
|
1861
2213
|
if (marking != null) {
|
|
1862
2214
|
trace.push(marking);
|
|
2215
|
+
const key = marking.toString();
|
|
2216
|
+
if (!stateByKey.has(key)) stateByKey.set(key, marking);
|
|
2217
|
+
} else {
|
|
2218
|
+
counters.skipped++;
|
|
1863
2219
|
}
|
|
1864
2220
|
}
|
|
1865
2221
|
}
|
|
@@ -1867,7 +2223,7 @@ function extractTrace(ctx, expr, flatNet, trace, transitions) {
|
|
|
1867
2223
|
const numArgs = expr.numArgs();
|
|
1868
2224
|
for (let i = 0; i < numArgs; i++) {
|
|
1869
2225
|
const child = expr.arg(i);
|
|
1870
|
-
extractTrace(ctx, child, flatNet, trace, transitions);
|
|
2226
|
+
extractTrace(ctx, child, flatNet, trace, transitions, stateByKey, counters);
|
|
1871
2227
|
}
|
|
1872
2228
|
} catch {
|
|
1873
2229
|
}
|
|
@@ -1893,6 +2249,238 @@ function extractMarking(ctx, reachableApp, flatNet) {
|
|
|
1893
2249
|
return builder.build();
|
|
1894
2250
|
}
|
|
1895
2251
|
|
|
2252
|
+
// src/verification/z3/abstract-replayer.ts
|
|
2253
|
+
function stepName(step) {
|
|
2254
|
+
return step.kind === "fire" ? step.transition : `inject(${step.place})`;
|
|
2255
|
+
}
|
|
2256
|
+
function stateKey(state) {
|
|
2257
|
+
return state.join(",");
|
|
2258
|
+
}
|
|
2259
|
+
function vectorize(marking, flatNet) {
|
|
2260
|
+
return flatNet.places.map((p) => marking.tokens(p));
|
|
2261
|
+
}
|
|
2262
|
+
function toMarkingState(state, flatNet) {
|
|
2263
|
+
const builder = MarkingState.builder();
|
|
2264
|
+
for (let i = 0; i < flatNet.places.length; i++) {
|
|
2265
|
+
if (state[i] > 0) builder.tokens(flatNet.places[i], state[i]);
|
|
2266
|
+
}
|
|
2267
|
+
return builder.build();
|
|
2268
|
+
}
|
|
2269
|
+
function enabledA(state, ft) {
|
|
2270
|
+
const P = state.length;
|
|
2271
|
+
for (let p = 0; p < P; p++) {
|
|
2272
|
+
if (ft.preVector[p] > 0 && state[p] < ft.preVector[p]) return false;
|
|
2273
|
+
}
|
|
2274
|
+
for (const p of ft.readPlaces) {
|
|
2275
|
+
if (state[p] < 1) return false;
|
|
2276
|
+
}
|
|
2277
|
+
for (const p of ft.inhibitorPlaces) {
|
|
2278
|
+
if (state[p] !== 0) return false;
|
|
2279
|
+
}
|
|
2280
|
+
return true;
|
|
2281
|
+
}
|
|
2282
|
+
function fireIndexed(state, ft, resets) {
|
|
2283
|
+
const P = state.length;
|
|
2284
|
+
const next = new Array(P);
|
|
2285
|
+
for (let p = 0; p < P; p++) {
|
|
2286
|
+
if (resets.has(p) || ft.consumeAll[p]) {
|
|
2287
|
+
next[p] = ft.postVector[p];
|
|
2288
|
+
} else {
|
|
2289
|
+
next[p] = state[p] - ft.preVector[p] + ft.postVector[p];
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
return next;
|
|
2293
|
+
}
|
|
2294
|
+
function injectA(state, idx) {
|
|
2295
|
+
const next = [...state];
|
|
2296
|
+
next[idx] = next[idx] + 1;
|
|
2297
|
+
return next;
|
|
2298
|
+
}
|
|
2299
|
+
function buildIndex(flatNet) {
|
|
2300
|
+
const resetSets = flatNet.transitions.map((ft) => new Set(ft.resetPlaces));
|
|
2301
|
+
const envInj = /* @__PURE__ */ new Map();
|
|
2302
|
+
for (const [name, bound] of flatNet.environmentInjection) {
|
|
2303
|
+
const idx = flatNet.placeIndex.get(name);
|
|
2304
|
+
if (idx != null) envInj.set(idx, bound);
|
|
2305
|
+
}
|
|
2306
|
+
const envCaps = [];
|
|
2307
|
+
for (const [name, cap] of flatNet.environmentBounds) {
|
|
2308
|
+
const idx = flatNet.placeIndex.get(name);
|
|
2309
|
+
if (idx != null) envCaps.push([idx, cap]);
|
|
2310
|
+
}
|
|
2311
|
+
return { flatNet, resetSets, envInj, envCaps };
|
|
2312
|
+
}
|
|
2313
|
+
function withinEnvBounds(index, state) {
|
|
2314
|
+
for (const [idx, cap] of index.envCaps) {
|
|
2315
|
+
if (state[idx] > cap) return false;
|
|
2316
|
+
}
|
|
2317
|
+
return true;
|
|
2318
|
+
}
|
|
2319
|
+
function successorsIndexed(index, state) {
|
|
2320
|
+
const out = [];
|
|
2321
|
+
const transitions = index.flatNet.transitions;
|
|
2322
|
+
for (let t = 0; t < transitions.length; t++) {
|
|
2323
|
+
const ft = transitions[t];
|
|
2324
|
+
if (!enabledA(state, ft)) continue;
|
|
2325
|
+
const next = fireIndexed(state, ft, index.resetSets[t]);
|
|
2326
|
+
if (!withinEnvBounds(index, next)) continue;
|
|
2327
|
+
out.push({ state: next, step: { kind: "fire", transition: ft.name } });
|
|
2328
|
+
}
|
|
2329
|
+
for (const [name, bound] of index.flatNet.environmentInjection) {
|
|
2330
|
+
const idx = index.flatNet.placeIndex.get(name);
|
|
2331
|
+
if (idx == null) continue;
|
|
2332
|
+
if (bound === null || state[idx] < bound) {
|
|
2333
|
+
out.push({ state: injectA(state, idx), step: { kind: "inject", place: name } });
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
return out;
|
|
2337
|
+
}
|
|
2338
|
+
function enabledRelaxEnv(state, ft, envInj) {
|
|
2339
|
+
const P = state.length;
|
|
2340
|
+
for (let p = 0; p < P; p++) {
|
|
2341
|
+
const pre = ft.preVector[p];
|
|
2342
|
+
if (pre <= 0) continue;
|
|
2343
|
+
if (envInj.has(p)) {
|
|
2344
|
+
const bound = envInj.get(p);
|
|
2345
|
+
if (bound !== null && pre > bound) return false;
|
|
2346
|
+
continue;
|
|
2347
|
+
}
|
|
2348
|
+
if (state[p] < pre) return false;
|
|
2349
|
+
}
|
|
2350
|
+
for (const p of ft.readPlaces) {
|
|
2351
|
+
if (envInj.has(p)) {
|
|
2352
|
+
const bound = envInj.get(p);
|
|
2353
|
+
if (bound !== null && bound < 1) return false;
|
|
2354
|
+
continue;
|
|
2355
|
+
}
|
|
2356
|
+
if (state[p] < 1) return false;
|
|
2357
|
+
}
|
|
2358
|
+
for (const p of ft.inhibitorPlaces) {
|
|
2359
|
+
if (state[p] !== 0) return false;
|
|
2360
|
+
}
|
|
2361
|
+
return true;
|
|
2362
|
+
}
|
|
2363
|
+
function isDeadlockA(index, state) {
|
|
2364
|
+
for (const ft of index.flatNet.transitions) {
|
|
2365
|
+
if (enabledRelaxEnv(state, ft, index.envInj)) return false;
|
|
2366
|
+
}
|
|
2367
|
+
return true;
|
|
2368
|
+
}
|
|
2369
|
+
function satisfiesBadIndexed(index, state, property, sinkPlaces) {
|
|
2370
|
+
const flatNet = index.flatNet;
|
|
2371
|
+
switch (property.type) {
|
|
2372
|
+
case "deadlock-free": {
|
|
2373
|
+
if (!isDeadlockA(index, state)) return false;
|
|
2374
|
+
for (const sink of sinkPlaces) {
|
|
2375
|
+
const idx = flatNetIndexOf(flatNet, sink);
|
|
2376
|
+
if (idx >= 0 && state[idx] > 0) return false;
|
|
2377
|
+
}
|
|
2378
|
+
return true;
|
|
2379
|
+
}
|
|
2380
|
+
case "mutual-exclusion": {
|
|
2381
|
+
const idx1 = flatNetIndexOf(flatNet, property.p1);
|
|
2382
|
+
const idx2 = flatNetIndexOf(flatNet, property.p2);
|
|
2383
|
+
if (idx1 < 0 || idx2 < 0) return false;
|
|
2384
|
+
return state[idx1] >= 1 && state[idx2] >= 1;
|
|
2385
|
+
}
|
|
2386
|
+
case "place-bound":
|
|
2387
|
+
case "branch-place-bound": {
|
|
2388
|
+
const idx = flatNetIndexOf(flatNet, property.place);
|
|
2389
|
+
if (idx < 0) return false;
|
|
2390
|
+
return state[idx] > property.bound;
|
|
2391
|
+
}
|
|
2392
|
+
case "joined-or-dead-lettered": {
|
|
2393
|
+
const idx = flatNetIndexOf(flatNet, property.pending);
|
|
2394
|
+
if (idx < 0) return false;
|
|
2395
|
+
return isDeadlockA(index, state) && state[idx] >= 1;
|
|
2396
|
+
}
|
|
2397
|
+
case "unreachable": {
|
|
2398
|
+
let resolved = 0;
|
|
2399
|
+
for (const p of property.places) {
|
|
2400
|
+
const idx = flatNetIndexOf(flatNet, p);
|
|
2401
|
+
if (idx < 0) continue;
|
|
2402
|
+
resolved++;
|
|
2403
|
+
if (state[idx] < 1) return false;
|
|
2404
|
+
}
|
|
2405
|
+
return resolved > 0;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
function replayCounterexample(flatNet, initial, decodedStates, property, sinkPlaces, options = {}) {
|
|
2410
|
+
const segmentBudget = options.segmentBudget ?? 3;
|
|
2411
|
+
const nodeBudget = options.nodeBudget ?? 1e4;
|
|
2412
|
+
const anchors = /* @__PURE__ */ new Set();
|
|
2413
|
+
for (const s of decodedStates) anchors.add(stateKey(s));
|
|
2414
|
+
if (anchors.size === 0) {
|
|
2415
|
+
return { kind: "exhausted", reason: "no decoded states to replay", nodesExplored: 0 };
|
|
2416
|
+
}
|
|
2417
|
+
const initKey = stateKey(initial);
|
|
2418
|
+
if (!anchors.has(initKey)) {
|
|
2419
|
+
return {
|
|
2420
|
+
kind: "exhausted",
|
|
2421
|
+
reason: "the initial marking is not among the decoded states",
|
|
2422
|
+
nodesExplored: 0
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
const index = buildIndex(flatNet);
|
|
2426
|
+
if (satisfiesBadIndexed(index, initial, property, sinkPlaces)) {
|
|
2427
|
+
return { kind: "confirmed", states: [initial], steps: [], nodesExplored: 1 };
|
|
2428
|
+
}
|
|
2429
|
+
const nodes = [{ state: initial, step: null, parent: -1, segment: 0 }];
|
|
2430
|
+
const bestSegment = /* @__PURE__ */ new Map([[initKey, 0]]);
|
|
2431
|
+
const queue = [0];
|
|
2432
|
+
let truncated = false;
|
|
2433
|
+
for (let head = 0; head < queue.length; head++) {
|
|
2434
|
+
const idx = queue[head];
|
|
2435
|
+
const node = nodes[idx];
|
|
2436
|
+
if (node.segment >= segmentBudget) {
|
|
2437
|
+
truncated = true;
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
for (const succ of successorsIndexed(index, node.state)) {
|
|
2441
|
+
const key = stateKey(succ.state);
|
|
2442
|
+
const segment = anchors.has(key) ? 0 : node.segment + 1;
|
|
2443
|
+
const prior = bestSegment.get(key);
|
|
2444
|
+
if (prior !== void 0 && prior <= segment) continue;
|
|
2445
|
+
bestSegment.set(key, segment);
|
|
2446
|
+
if (nodes.length >= nodeBudget) {
|
|
2447
|
+
return {
|
|
2448
|
+
kind: "exhausted",
|
|
2449
|
+
reason: `search budget exhausted (${nodeBudget} nodes) before reaching a violating state`,
|
|
2450
|
+
nodesExplored: nodes.length
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
nodes.push({ state: succ.state, step: succ.step, parent: idx, segment });
|
|
2454
|
+
const childIdx = nodes.length - 1;
|
|
2455
|
+
if (satisfiesBadIndexed(index, succ.state, property, sinkPlaces)) {
|
|
2456
|
+
const chain = reconstruct(nodes, childIdx);
|
|
2457
|
+
return { kind: "confirmed", ...chain, nodesExplored: nodes.length };
|
|
2458
|
+
}
|
|
2459
|
+
queue.push(childIdx);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
if (truncated) {
|
|
2463
|
+
return {
|
|
2464
|
+
kind: "exhausted",
|
|
2465
|
+
reason: `no violating state within ${segmentBudget} abstract step(s) of a decoded state (${bestSegment.size} state(s) explored)`,
|
|
2466
|
+
nodesExplored: nodes.length
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
return { kind: "no-chain", nodesExplored: nodes.length };
|
|
2470
|
+
}
|
|
2471
|
+
function reconstruct(nodes, last) {
|
|
2472
|
+
const states = [];
|
|
2473
|
+
const steps = [];
|
|
2474
|
+
for (let i = last; i >= 0; i = nodes[i].parent) {
|
|
2475
|
+
const node = nodes[i];
|
|
2476
|
+
states.push(node.state);
|
|
2477
|
+
if (node.step != null) steps.push(node.step);
|
|
2478
|
+
}
|
|
2479
|
+
states.reverse();
|
|
2480
|
+
steps.reverse();
|
|
2481
|
+
return { states, steps };
|
|
2482
|
+
}
|
|
2483
|
+
|
|
1896
2484
|
// src/verification/z3/name-coloured-encoder.ts
|
|
1897
2485
|
function colourSlotBound(coloured, semiflows) {
|
|
1898
2486
|
const w = (inv, pid) => inv.weights[pid] ?? 0;
|
|
@@ -2366,9 +2954,9 @@ function classify(net, mode, carrierPlaces) {
|
|
|
2366
2954
|
role: (tn) => roles.get(tn) ?? { type: "ordinary" }
|
|
2367
2955
|
};
|
|
2368
2956
|
}
|
|
2369
|
-
function fixedRequiredCount(t,
|
|
2957
|
+
function fixedRequiredCount(t, placeName2) {
|
|
2370
2958
|
for (const spec of t.inputSpecs) {
|
|
2371
|
-
if (spec.place.name ===
|
|
2959
|
+
if (spec.place.name === placeName2) {
|
|
2372
2960
|
switch (spec.type) {
|
|
2373
2961
|
case "one":
|
|
2374
2962
|
return 1;
|
|
@@ -2602,10 +3190,10 @@ function sharesConsumedInput(h, l, marking) {
|
|
|
2602
3190
|
}
|
|
2603
3191
|
return false;
|
|
2604
3192
|
}
|
|
2605
|
-
function consumedDemand(t,
|
|
3193
|
+
function consumedDemand(t, placeName2) {
|
|
2606
3194
|
let demand = 0;
|
|
2607
3195
|
for (const spec of t.inputSpecs) {
|
|
2608
|
-
if (spec.place.name ===
|
|
3196
|
+
if (spec.place.name === placeName2) demand += inputRequiredCount2(spec);
|
|
2609
3197
|
}
|
|
2610
3198
|
return demand;
|
|
2611
3199
|
}
|
|
@@ -2799,6 +3387,8 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2799
3387
|
_budgetPlaces = /* @__PURE__ */ new Set();
|
|
2800
3388
|
_environmentMode = alwaysAvailable();
|
|
2801
3389
|
_timeoutMs = 6e4;
|
|
3390
|
+
_certificateCheck = true;
|
|
3391
|
+
_counterexampleReplay = true;
|
|
2802
3392
|
_nuMaxClasses = 1e5;
|
|
2803
3393
|
_fragmentMode = "base";
|
|
2804
3394
|
_carrierPlaces = /* @__PURE__ */ new Set();
|
|
@@ -2852,6 +3442,34 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2852
3442
|
this._timeoutMs = ms;
|
|
2853
3443
|
return this;
|
|
2854
3444
|
}
|
|
3445
|
+
/**
|
|
3446
|
+
* Enables/disables the independent IC3 certificate check (default: enabled).
|
|
3447
|
+
*
|
|
3448
|
+
* When a proven verdict comes from the IC3/Spacer path on the flat count
|
|
3449
|
+
* encoding, the synthesized inductive invariant is re-validated with a plain
|
|
3450
|
+
* solver against the UNSTRENGTHENED step relation — VC1 (init), VC2
|
|
3451
|
+
* (consecution), VC3 (safety) — so a Spacer or encoder defect cannot certify
|
|
3452
|
+
* a false PROVEN. A certificate that fails validation downgrades the verdict
|
|
3453
|
+
* to unknown. Structural proofs and the coloured ν-encoding are unaffected.
|
|
3454
|
+
*/
|
|
3455
|
+
certificateCheck(enabled) {
|
|
3456
|
+
this._certificateCheck = enabled;
|
|
3457
|
+
return this;
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* Enables/disables abstract counterexample replay (default: enabled).
|
|
3461
|
+
*
|
|
3462
|
+
* When a violated verdict comes from the flat count encoding, the decoded
|
|
3463
|
+
* counterexample states (an order-free set — the derivation tree is walked in
|
|
3464
|
+
* traversal order, not firing order) are re-executed TS-side against the
|
|
3465
|
+
* abstract semantics the encoder emits (Lean's `fireA`, Basic.lean), searching
|
|
3466
|
+
* for a firing order from M₀ to a property-violating marking. See
|
|
3467
|
+
* `SmtVerificationResult.counterexampleConfirmed` for how each outcome lands.
|
|
3468
|
+
*/
|
|
3469
|
+
counterexampleReplay(enabled) {
|
|
3470
|
+
this._counterexampleReplay = enabled;
|
|
3471
|
+
return this;
|
|
3472
|
+
}
|
|
2855
3473
|
/**
|
|
2856
3474
|
* Sets the class-count cap for the ν-aware state-class-graph analysis (NU-050,
|
|
2857
3475
|
* Route B). When the symbolic name-aware graph would exceed this, the analysis
|
|
@@ -2998,6 +3616,7 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
2998
3616
|
report.push("=== RESULT ===\n");
|
|
2999
3617
|
report.push("PROVEN (structural): Deadlock-freedom verified by Commoner's theorem.");
|
|
3000
3618
|
report.push(" All siphons contain initially marked traps.");
|
|
3619
|
+
report.push(" Certificate check: not applicable (structural proof)");
|
|
3001
3620
|
return buildResult(
|
|
3002
3621
|
{ type: "proven", method: "structural", inductiveInvariant: null },
|
|
3003
3622
|
report.join("\n"),
|
|
@@ -3011,14 +3630,36 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3011
3630
|
}
|
|
3012
3631
|
report.push("Phase 3: Computing P-invariants...");
|
|
3013
3632
|
const matrix = IncidenceMatrix.from(flatNet);
|
|
3014
|
-
const invariants =
|
|
3015
|
-
|
|
3633
|
+
const { valid: invariants, dropped: droppedInvariants } = validateInvariantsExact(
|
|
3634
|
+
matrix,
|
|
3635
|
+
computePInvariants(matrix, flatNet, this._initialMarking),
|
|
3636
|
+
flatNet,
|
|
3637
|
+
this._initialMarking
|
|
3638
|
+
);
|
|
3639
|
+
const { valid: semiflows, dropped: droppedSemiflows } = validateInvariantsExact(
|
|
3640
|
+
matrix,
|
|
3641
|
+
computePSemiflows(matrix, flatNet, this._initialMarking),
|
|
3642
|
+
flatNet,
|
|
3643
|
+
this._initialMarking
|
|
3644
|
+
);
|
|
3016
3645
|
report.push(` Found: ${invariants.length} P-invariant(s)`);
|
|
3017
3646
|
const structurallyBounded = isCoveredByInvariants(invariants, flatNet.places.length);
|
|
3018
3647
|
report.push(` Structurally bounded: ${structurallyBounded ? "YES" : "NO"}`);
|
|
3019
3648
|
for (const inv of invariants) {
|
|
3020
3649
|
report.push(` ${formatInvariant(inv, flatNet)}`);
|
|
3021
3650
|
}
|
|
3651
|
+
for (const { invariant, reason } of droppedInvariants) {
|
|
3652
|
+
report.push(` Dropped invariant: ${formatInvariant(invariant, flatNet)} - ${reason}`);
|
|
3653
|
+
}
|
|
3654
|
+
if (droppedInvariants.length > 0) {
|
|
3655
|
+
report.push(` Dropped: ${droppedInvariants.length} invariant(s) failed the exact re-check`);
|
|
3656
|
+
}
|
|
3657
|
+
for (const { invariant, reason } of droppedSemiflows) {
|
|
3658
|
+
report.push(` Dropped semiflow: ${formatInvariant(invariant, flatNet)} - ${reason}`);
|
|
3659
|
+
}
|
|
3660
|
+
if (droppedSemiflows.length > 0) {
|
|
3661
|
+
report.push(` Dropped: ${droppedSemiflows.length} semiflow(s) failed the exact re-check`);
|
|
3662
|
+
}
|
|
3022
3663
|
report.push("");
|
|
3023
3664
|
report.push("Phase 4: IC3/PDR verification via Z3 Spacer...");
|
|
3024
3665
|
const colouredPlan = hasMatch && nuBounded ? buildColouredPlan(
|
|
@@ -3095,7 +3736,45 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3095
3736
|
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3096
3737
|
);
|
|
3097
3738
|
}
|
|
3098
|
-
report.push(" Status: UNSAT (property holds)
|
|
3739
|
+
report.push(" Status: UNSAT (property holds)");
|
|
3740
|
+
if (colouredPlan != null) {
|
|
3741
|
+
report.push(" Certificate check: not applicable (name-coloured encoding)");
|
|
3742
|
+
} else if (!this._certificateCheck) {
|
|
3743
|
+
report.push(" Certificate check: not applicable (disabled)");
|
|
3744
|
+
} else {
|
|
3745
|
+
const certificate = await checkCertificate(
|
|
3746
|
+
runner.ctx,
|
|
3747
|
+
queryResult.answer,
|
|
3748
|
+
flatNet,
|
|
3749
|
+
this._initialMarking,
|
|
3750
|
+
this._property,
|
|
3751
|
+
invariants,
|
|
3752
|
+
this._sinkPlaces,
|
|
3753
|
+
this._timeoutMs
|
|
3754
|
+
);
|
|
3755
|
+
const reason = certificateDowngradeReason(certificate);
|
|
3756
|
+
if (reason != null) {
|
|
3757
|
+
report.push(" Certificate check: FAILED");
|
|
3758
|
+
if (certificate.type !== "passed" && certificate.invariant != null) {
|
|
3759
|
+
report.push(` Uncertified invariant: ${substituteNames(certificate.invariant, flatNet)}`);
|
|
3760
|
+
}
|
|
3761
|
+
report.push("");
|
|
3762
|
+
report.push("=== RESULT ===\n");
|
|
3763
|
+
report.push(`UNKNOWN: ${reason}`);
|
|
3764
|
+
return buildResult(
|
|
3765
|
+
{ type: "unknown", reason },
|
|
3766
|
+
report.join("\n"),
|
|
3767
|
+
invariants,
|
|
3768
|
+
[],
|
|
3769
|
+
[],
|
|
3770
|
+
[],
|
|
3771
|
+
performance.now() - start,
|
|
3772
|
+
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
3773
|
+
);
|
|
3774
|
+
}
|
|
3775
|
+
report.push(" Certificate check: PASSED (init, consecution, safety)");
|
|
3776
|
+
}
|
|
3777
|
+
report.push("");
|
|
3099
3778
|
const discoveredInvariants = [];
|
|
3100
3779
|
if (queryResult.invariantFormula != null) {
|
|
3101
3780
|
discoveredInvariants.push(substituteNames(queryResult.invariantFormula, flatNet));
|
|
@@ -3138,16 +3817,79 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3138
3817
|
case "violated": {
|
|
3139
3818
|
report.push(" Status: SAT (counterexample found)\n");
|
|
3140
3819
|
const decoded = decode(runner.ctx, queryResult.answer, flatNet);
|
|
3820
|
+
const stats = {
|
|
3821
|
+
places: flatNet.places.length,
|
|
3822
|
+
transitions: flatNet.transitions.length,
|
|
3823
|
+
invariantsFound: invariants.length,
|
|
3824
|
+
structuralResult: structResultStr
|
|
3825
|
+
};
|
|
3826
|
+
let confirmed = null;
|
|
3827
|
+
let trace = decoded.trace;
|
|
3828
|
+
let transitions = decoded.transitions;
|
|
3829
|
+
let replayed = false;
|
|
3830
|
+
if (colouredPlan == null && this._counterexampleReplay) {
|
|
3831
|
+
const assessment = assessCounterexample(
|
|
3832
|
+
flatNet,
|
|
3833
|
+
this._initialMarking,
|
|
3834
|
+
decoded.states,
|
|
3835
|
+
this._property,
|
|
3836
|
+
this._sinkPlaces
|
|
3837
|
+
);
|
|
3838
|
+
if (assessment.kind === "confirmed") {
|
|
3839
|
+
confirmed = true;
|
|
3840
|
+
replayed = true;
|
|
3841
|
+
trace = assessment.trace;
|
|
3842
|
+
transitions = assessment.firings;
|
|
3843
|
+
report.push(" Counterexample replay: CONFIRMED (abstract chain M0 -> bad re-executed)");
|
|
3844
|
+
} else if (assessment.kind === "unconfirmed") {
|
|
3845
|
+
confirmed = false;
|
|
3846
|
+
report.push(` Counterexample replay: UNCONFIRMED (${assessment.note})`);
|
|
3847
|
+
if (decoded.failure != null) {
|
|
3848
|
+
report.push(` Decoder degradation: ${describeDecodeFailure(decoded.failure)}`);
|
|
3849
|
+
}
|
|
3850
|
+
report.push(" The verdict rests on Spacer's SAT answer.");
|
|
3851
|
+
} else {
|
|
3852
|
+
report.push(" Counterexample replay: FAILED");
|
|
3853
|
+
report.push(` Decoded states (order-free set, ${decoded.states.size}):`);
|
|
3854
|
+
for (const m of decoded.states) {
|
|
3855
|
+
report.push(` ${m}`);
|
|
3856
|
+
}
|
|
3857
|
+
if (decoded.trace.length > 0) {
|
|
3858
|
+
report.push(` Raw traversal-order trace (${decoded.trace.length} states):`);
|
|
3859
|
+
for (let i = 0; i < decoded.trace.length; i++) {
|
|
3860
|
+
report.push(` ${i}: ${decoded.trace[i]}`);
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
if (decoded.failure != null) {
|
|
3864
|
+
report.push(` Decoder degradation: ${describeDecodeFailure(decoded.failure)}`);
|
|
3865
|
+
}
|
|
3866
|
+
report.push(` Raw Z3 answer: ${truncate(String(queryResult.answer), 2e3)}`);
|
|
3867
|
+
report.push("");
|
|
3868
|
+
report.push("=== RESULT ===\n");
|
|
3869
|
+
report.push(`UNKNOWN: ${assessment.reason}`);
|
|
3870
|
+
return buildResult(
|
|
3871
|
+
{ type: "unknown", reason: assessment.reason },
|
|
3872
|
+
report.join("\n"),
|
|
3873
|
+
invariants,
|
|
3874
|
+
[],
|
|
3875
|
+
[],
|
|
3876
|
+
[],
|
|
3877
|
+
performance.now() - start,
|
|
3878
|
+
stats,
|
|
3879
|
+
false
|
|
3880
|
+
);
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3141
3883
|
report.push("=== RESULT ===\n");
|
|
3142
3884
|
report.push(`VIOLATED: ${propDesc}`);
|
|
3143
|
-
if (
|
|
3144
|
-
report.push(` Counterexample trace (${
|
|
3145
|
-
for (let i = 0; i <
|
|
3146
|
-
report.push(` ${i}: ${
|
|
3885
|
+
if (trace.length > 0) {
|
|
3886
|
+
report.push(` Counterexample trace (${replayed ? "replay order, " : ""}${trace.length} states):`);
|
|
3887
|
+
for (let i = 0; i < trace.length; i++) {
|
|
3888
|
+
report.push(` ${i}: ${trace[i]}`);
|
|
3147
3889
|
}
|
|
3148
3890
|
}
|
|
3149
|
-
if (
|
|
3150
|
-
report.push(` Firing sequence: ${
|
|
3891
|
+
if (transitions.length > 0) {
|
|
3892
|
+
report.push(` Firing sequence: ${transitions.join(" -> ")}`);
|
|
3151
3893
|
}
|
|
3152
3894
|
report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
|
|
3153
3895
|
report.push(" It may be spurious if timing constraints prevent this sequence.");
|
|
@@ -3156,10 +3898,11 @@ var SmtVerifier = class _SmtVerifier {
|
|
|
3156
3898
|
report.join("\n"),
|
|
3157
3899
|
invariants,
|
|
3158
3900
|
[],
|
|
3159
|
-
|
|
3160
|
-
|
|
3901
|
+
trace,
|
|
3902
|
+
transitions,
|
|
3161
3903
|
performance.now() - start,
|
|
3162
|
-
|
|
3904
|
+
stats,
|
|
3905
|
+
confirmed
|
|
3163
3906
|
), hasMatch, nuBounded, colouredPlan != null);
|
|
3164
3907
|
}
|
|
3165
3908
|
case "unknown": {
|
|
@@ -3252,6 +3995,51 @@ function isReachabilitySafety(property) {
|
|
|
3252
3995
|
return false;
|
|
3253
3996
|
}
|
|
3254
3997
|
}
|
|
3998
|
+
function assessCounterexample(flatNet, initialMarking, decodedStates, property, sinkPlaces) {
|
|
3999
|
+
if (decodedStates.size === 0) {
|
|
4000
|
+
return {
|
|
4001
|
+
kind: "unconfirmed",
|
|
4002
|
+
note: "no counterexample states could be decoded from the Spacer answer, so the abstract replay could not run"
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4005
|
+
let outcome;
|
|
4006
|
+
try {
|
|
4007
|
+
outcome = replayCounterexample(
|
|
4008
|
+
flatNet,
|
|
4009
|
+
vectorize(initialMarking, flatNet),
|
|
4010
|
+
[...decodedStates].map((m) => vectorize(m, flatNet)),
|
|
4011
|
+
property,
|
|
4012
|
+
sinkPlaces
|
|
4013
|
+
);
|
|
4014
|
+
} catch (e) {
|
|
4015
|
+
outcome = { kind: "exhausted", reason: `replay threw: ${e?.message ?? e}`, nodesExplored: 0 };
|
|
4016
|
+
}
|
|
4017
|
+
switch (outcome.kind) {
|
|
4018
|
+
case "confirmed":
|
|
4019
|
+
return {
|
|
4020
|
+
kind: "confirmed",
|
|
4021
|
+
trace: outcome.states.map((s) => toMarkingState(s, flatNet)),
|
|
4022
|
+
firings: outcome.steps.map(stepName)
|
|
4023
|
+
};
|
|
4024
|
+
case "exhausted":
|
|
4025
|
+
return { kind: "unconfirmed", note: `abstract replay did not complete: ${outcome.reason}` };
|
|
4026
|
+
case "no-chain":
|
|
4027
|
+
return {
|
|
4028
|
+
kind: "downgraded",
|
|
4029
|
+
reason: "counterexample replay found no firing chain to the violation under the abstract semantics, so VIOLATED is withheld"
|
|
4030
|
+
};
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
function certificateDowngradeReason(outcome) {
|
|
4034
|
+
switch (outcome.type) {
|
|
4035
|
+
case "passed":
|
|
4036
|
+
return null;
|
|
4037
|
+
case "failed":
|
|
4038
|
+
return `certificate check failed: ${outcome.vc} was not UNSAT - ${outcome.detail}; the IC3 certificate could not be independently re-validated against the unstrengthened step relation, so PROVEN is withheld`;
|
|
4039
|
+
case "unavailable":
|
|
4040
|
+
return `certificate check could not run: ${outcome.reason}; PROVEN is withheld without an independently validated certificate`;
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
3255
4043
|
function downgradeToUnknown(result, reason) {
|
|
3256
4044
|
return {
|
|
3257
4045
|
...result,
|
|
@@ -3261,9 +4049,13 @@ Downgraded to UNKNOWN: ${reason}
|
|
|
3261
4049
|
`,
|
|
3262
4050
|
discoveredInvariants: [],
|
|
3263
4051
|
counterexampleTrace: [],
|
|
3264
|
-
counterexampleTransitions: []
|
|
4052
|
+
counterexampleTransitions: [],
|
|
4053
|
+
counterexampleConfirmed: null
|
|
3265
4054
|
};
|
|
3266
4055
|
}
|
|
4056
|
+
function truncate(s, max) {
|
|
4057
|
+
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (${s.length - max} chars truncated)`;
|
|
4058
|
+
}
|
|
3267
4059
|
function substituteNames(formula, flatNet) {
|
|
3268
4060
|
for (let i = flatNet.places.length - 1; i >= 0; i--) {
|
|
3269
4061
|
formula = formula.replace(new RegExp(`\\bm${i}\\b`, "g"), flatNet.places[i].name);
|
|
@@ -3279,10 +4071,10 @@ function formatInvariant(inv, flatNet) {
|
|
|
3279
4071
|
parts.push(flatNet.places[idx].name);
|
|
3280
4072
|
}
|
|
3281
4073
|
}
|
|
3282
|
-
return `${parts.join(" + ")} = ${inv.constant}`;
|
|
4074
|
+
return `${parts.length === 0 ? "0" : parts.join(" + ")} = ${inv.constant}`;
|
|
3283
4075
|
}
|
|
3284
|
-
function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics) {
|
|
3285
|
-
return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, elapsedMs, statistics };
|
|
4076
|
+
function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics, counterexampleConfirmed = null) {
|
|
4077
|
+
return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, counterexampleConfirmed, elapsedMs, statistics };
|
|
3286
4078
|
}
|
|
3287
4079
|
|
|
3288
4080
|
// src/verification/smt-verification-result.ts
|
|
@@ -3346,13 +4138,16 @@ export {
|
|
|
3346
4138
|
flatNetTransitionCount,
|
|
3347
4139
|
flatNetIndexOf,
|
|
3348
4140
|
encode,
|
|
4141
|
+
checkCertificate,
|
|
3349
4142
|
DBM,
|
|
3350
4143
|
StateClass,
|
|
3351
4144
|
requireOutputProducingActions,
|
|
3352
4145
|
StateClassGraph,
|
|
4146
|
+
describeDecodeFailure,
|
|
3353
4147
|
decode,
|
|
4148
|
+
replayCounterexample,
|
|
3354
4149
|
SmtVerifier,
|
|
3355
4150
|
isProven,
|
|
3356
4151
|
isViolated
|
|
3357
4152
|
};
|
|
3358
|
-
//# sourceMappingURL=chunk-
|
|
4153
|
+
//# sourceMappingURL=chunk-JZIEWVAV.js.map
|