libpetri 0.3.2
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 +121 -0
- package/dist/chunk-FN773SSE.js +87 -0
- package/dist/chunk-FN773SSE.js.map +1 -0
- package/dist/chunk-VQ4XMJTD.js +107 -0
- package/dist/chunk-VQ4XMJTD.js.map +1 -0
- package/dist/export/index.d.ts +153 -0
- package/dist/export/index.js +411 -0
- package/dist/export/index.js.map +1 -0
- package/dist/index.d.ts +498 -0
- package/dist/index.js +1972 -0
- package/dist/index.js.map +1 -0
- package/dist/petri-net-C3Jy5HCt.d.ts +543 -0
- package/dist/verification/index.d.ts +505 -0
- package/dist/verification/index.js +1201 -0
- package/dist/verification/index.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
allPlaces,
|
|
3
|
+
enumerateBranches
|
|
4
|
+
} from "../chunk-VQ4XMJTD.js";
|
|
5
|
+
|
|
6
|
+
// src/verification/marking-state.ts
|
|
7
|
+
var MARKING_STATE_KEY = /* @__PURE__ */ Symbol("MarkingState.internal");
|
|
8
|
+
var MarkingState = class _MarkingState {
|
|
9
|
+
tokenCounts;
|
|
10
|
+
placesByName;
|
|
11
|
+
/** @internal Use {@link MarkingState.builder} or {@link MarkingState.empty} to create instances. */
|
|
12
|
+
constructor(key, tokenCounts, placesByName) {
|
|
13
|
+
if (key !== MARKING_STATE_KEY) throw new Error("Use MarkingState.builder() to create instances");
|
|
14
|
+
this.tokenCounts = tokenCounts;
|
|
15
|
+
this.placesByName = placesByName;
|
|
16
|
+
}
|
|
17
|
+
/** Returns the token count for a place (0 if absent). */
|
|
18
|
+
tokens(place) {
|
|
19
|
+
return this.tokenCounts.get(place.name) ?? 0;
|
|
20
|
+
}
|
|
21
|
+
/** Checks if a place has at least one token. */
|
|
22
|
+
hasTokens(place) {
|
|
23
|
+
return this.tokens(place) > 0;
|
|
24
|
+
}
|
|
25
|
+
/** Checks if any of the given places has tokens. */
|
|
26
|
+
hasTokensInAny(places) {
|
|
27
|
+
for (const p of places) {
|
|
28
|
+
if (this.hasTokens(p)) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/** Returns all places with tokens > 0. */
|
|
33
|
+
placesWithTokens() {
|
|
34
|
+
return [...this.placesByName.values()];
|
|
35
|
+
}
|
|
36
|
+
/** Returns the total number of tokens. */
|
|
37
|
+
totalTokens() {
|
|
38
|
+
let sum = 0;
|
|
39
|
+
for (const count of this.tokenCounts.values()) sum += count;
|
|
40
|
+
return sum;
|
|
41
|
+
}
|
|
42
|
+
/** Checks if no tokens exist anywhere. */
|
|
43
|
+
isEmpty() {
|
|
44
|
+
return this.tokenCounts.size === 0;
|
|
45
|
+
}
|
|
46
|
+
toString() {
|
|
47
|
+
if (this.tokenCounts.size === 0) return "{}";
|
|
48
|
+
const entries = [...this.tokenCounts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => `${name}:${count}`);
|
|
49
|
+
return `{${entries.join(", ")}}`;
|
|
50
|
+
}
|
|
51
|
+
static empty() {
|
|
52
|
+
return new _MarkingState(MARKING_STATE_KEY, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
|
|
53
|
+
}
|
|
54
|
+
static builder() {
|
|
55
|
+
return new MarkingStateBuilder();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var MarkingStateBuilder = class {
|
|
59
|
+
tokenCounts = /* @__PURE__ */ new Map();
|
|
60
|
+
placesByName = /* @__PURE__ */ new Map();
|
|
61
|
+
/** Sets the token count for a place. */
|
|
62
|
+
tokens(place, count) {
|
|
63
|
+
if (count < 0) throw new Error(`Token count cannot be negative: ${count}`);
|
|
64
|
+
if (count > 0) {
|
|
65
|
+
this.tokenCounts.set(place.name, count);
|
|
66
|
+
this.placesByName.set(place.name, place);
|
|
67
|
+
} else {
|
|
68
|
+
this.tokenCounts.delete(place.name);
|
|
69
|
+
this.placesByName.delete(place.name);
|
|
70
|
+
}
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
/** Adds tokens to a place. */
|
|
74
|
+
addTokens(place, count) {
|
|
75
|
+
if (count < 0) throw new Error(`Token count cannot be negative: ${count}`);
|
|
76
|
+
if (count > 0) {
|
|
77
|
+
const current = this.tokenCounts.get(place.name) ?? 0;
|
|
78
|
+
this.tokenCounts.set(place.name, current + count);
|
|
79
|
+
this.placesByName.set(place.name, place);
|
|
80
|
+
}
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
build() {
|
|
84
|
+
return new MarkingState(MARKING_STATE_KEY, new Map(this.tokenCounts), new Map(this.placesByName));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/verification/encoding/flat-transition.ts
|
|
89
|
+
function flatTransition(name, source, branchIndex, preVector, postVector, inhibitorPlaces, readPlaces, resetPlaces, consumeAll) {
|
|
90
|
+
return {
|
|
91
|
+
name,
|
|
92
|
+
source,
|
|
93
|
+
branchIndex,
|
|
94
|
+
preVector,
|
|
95
|
+
postVector,
|
|
96
|
+
inhibitorPlaces,
|
|
97
|
+
readPlaces,
|
|
98
|
+
resetPlaces,
|
|
99
|
+
consumeAll
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/verification/encoding/flat-net.ts
|
|
104
|
+
function flatNetPlaceCount(net) {
|
|
105
|
+
return net.places.length;
|
|
106
|
+
}
|
|
107
|
+
function flatNetTransitionCount(net) {
|
|
108
|
+
return net.transitions.length;
|
|
109
|
+
}
|
|
110
|
+
function flatNetIndexOf(net, place) {
|
|
111
|
+
return net.placeIndex.get(place.name) ?? -1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/verification/encoding/incidence-matrix.ts
|
|
115
|
+
var IncidenceMatrix = class _IncidenceMatrix {
|
|
116
|
+
_pre;
|
|
117
|
+
_post;
|
|
118
|
+
_incidence;
|
|
119
|
+
_numTransitions;
|
|
120
|
+
_numPlaces;
|
|
121
|
+
constructor(pre, post, incidence, numTransitions, numPlaces) {
|
|
122
|
+
this._pre = pre;
|
|
123
|
+
this._post = post;
|
|
124
|
+
this._incidence = incidence;
|
|
125
|
+
this._numTransitions = numTransitions;
|
|
126
|
+
this._numPlaces = numPlaces;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Computes the incidence matrix from a FlatNet.
|
|
130
|
+
*/
|
|
131
|
+
static from(flatNet) {
|
|
132
|
+
const T = flatNet.transitions.length;
|
|
133
|
+
const P = flatNet.places.length;
|
|
134
|
+
const pre = [];
|
|
135
|
+
const post = [];
|
|
136
|
+
const incidence = [];
|
|
137
|
+
for (let t = 0; t < T; t++) {
|
|
138
|
+
const ft = flatNet.transitions[t];
|
|
139
|
+
const preRow = new Array(P);
|
|
140
|
+
const postRow = new Array(P);
|
|
141
|
+
const incRow = new Array(P);
|
|
142
|
+
for (let p = 0; p < P; p++) {
|
|
143
|
+
preRow[p] = ft.preVector[p];
|
|
144
|
+
postRow[p] = ft.postVector[p];
|
|
145
|
+
incRow[p] = postRow[p] - preRow[p];
|
|
146
|
+
}
|
|
147
|
+
pre.push(preRow);
|
|
148
|
+
post.push(postRow);
|
|
149
|
+
incidence.push(incRow);
|
|
150
|
+
}
|
|
151
|
+
return new _IncidenceMatrix(pre, post, incidence, T, P);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Returns C^T (transpose of incidence matrix), dimensions [P][T].
|
|
155
|
+
* Used for P-invariant computation: null space of C^T gives P-invariants.
|
|
156
|
+
*/
|
|
157
|
+
transposedIncidence() {
|
|
158
|
+
const ct = [];
|
|
159
|
+
for (let p = 0; p < this._numPlaces; p++) {
|
|
160
|
+
const row = new Array(this._numTransitions);
|
|
161
|
+
for (let t = 0; t < this._numTransitions; t++) {
|
|
162
|
+
row[t] = this._incidence[t][p];
|
|
163
|
+
}
|
|
164
|
+
ct.push(row);
|
|
165
|
+
}
|
|
166
|
+
return ct;
|
|
167
|
+
}
|
|
168
|
+
/** Returns the pre-matrix (tokens consumed). T×P. */
|
|
169
|
+
pre() {
|
|
170
|
+
return this._pre;
|
|
171
|
+
}
|
|
172
|
+
/** Returns the post-matrix (tokens produced). T×P. */
|
|
173
|
+
post() {
|
|
174
|
+
return this._post;
|
|
175
|
+
}
|
|
176
|
+
/** Returns the incidence matrix C[t][p] = post - pre. T×P. */
|
|
177
|
+
incidence() {
|
|
178
|
+
return this._incidence;
|
|
179
|
+
}
|
|
180
|
+
numTransitions() {
|
|
181
|
+
return this._numTransitions;
|
|
182
|
+
}
|
|
183
|
+
numPlaces() {
|
|
184
|
+
return this._numPlaces;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/verification/encoding/net-flattener.ts
|
|
189
|
+
function unbounded() {
|
|
190
|
+
return { type: "unbounded" };
|
|
191
|
+
}
|
|
192
|
+
function bounded(maxTokens) {
|
|
193
|
+
return { type: "bounded", maxTokens };
|
|
194
|
+
}
|
|
195
|
+
function flatten(net, environmentPlaces = /* @__PURE__ */ new Set(), environmentMode = unbounded()) {
|
|
196
|
+
const allPlacesSet = /* @__PURE__ */ new Map();
|
|
197
|
+
for (const p of net.places) {
|
|
198
|
+
allPlacesSet.set(p.name, p);
|
|
199
|
+
}
|
|
200
|
+
for (const t of net.transitions) {
|
|
201
|
+
for (const inSpec of t.inputSpecs) {
|
|
202
|
+
allPlacesSet.set(inSpec.place.name, inSpec.place);
|
|
203
|
+
}
|
|
204
|
+
if (t.outputSpec !== null) {
|
|
205
|
+
for (const p of allPlaces(t.outputSpec)) {
|
|
206
|
+
allPlacesSet.set(p.name, p);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
for (const arc of t.inhibitors) allPlacesSet.set(arc.place.name, arc.place);
|
|
210
|
+
for (const arc of t.reads) allPlacesSet.set(arc.place.name, arc.place);
|
|
211
|
+
for (const arc of t.resets) allPlacesSet.set(arc.place.name, arc.place);
|
|
212
|
+
}
|
|
213
|
+
const places = [...allPlacesSet.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
214
|
+
const placeIndex = /* @__PURE__ */ new Map();
|
|
215
|
+
for (let i = 0; i < places.length; i++) {
|
|
216
|
+
placeIndex.set(places[i].name, i);
|
|
217
|
+
}
|
|
218
|
+
const environmentBounds = /* @__PURE__ */ new Map();
|
|
219
|
+
if (environmentMode.type === "bounded") {
|
|
220
|
+
for (const ep of environmentPlaces) {
|
|
221
|
+
environmentBounds.set(ep.place.name, environmentMode.maxTokens);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const n = places.length;
|
|
225
|
+
const flatTransitions = [];
|
|
226
|
+
for (const transition of net.transitions) {
|
|
227
|
+
const branches = enumerateOutputBranches(transition);
|
|
228
|
+
for (let branchIdx = 0; branchIdx < branches.length; branchIdx++) {
|
|
229
|
+
const branchPlaces = branches[branchIdx];
|
|
230
|
+
const name = branches.length > 1 ? `${transition.name}_b${branchIdx}` : transition.name;
|
|
231
|
+
const preVector = new Array(n).fill(0);
|
|
232
|
+
const consumeAll = new Array(n).fill(false);
|
|
233
|
+
for (const inSpec of transition.inputSpecs) {
|
|
234
|
+
const idx = placeIndex.get(inSpec.place.name);
|
|
235
|
+
if (idx === void 0) continue;
|
|
236
|
+
switch (inSpec.type) {
|
|
237
|
+
case "one":
|
|
238
|
+
preVector[idx] = 1;
|
|
239
|
+
break;
|
|
240
|
+
case "exactly":
|
|
241
|
+
preVector[idx] = inSpec.count;
|
|
242
|
+
break;
|
|
243
|
+
case "all":
|
|
244
|
+
preVector[idx] = 1;
|
|
245
|
+
consumeAll[idx] = true;
|
|
246
|
+
break;
|
|
247
|
+
case "at-least":
|
|
248
|
+
preVector[idx] = inSpec.minimum;
|
|
249
|
+
consumeAll[idx] = true;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const postVector = new Array(n).fill(0);
|
|
254
|
+
for (const p of branchPlaces) {
|
|
255
|
+
const idx = placeIndex.get(p.name);
|
|
256
|
+
if (idx !== void 0) {
|
|
257
|
+
postVector[idx] = 1;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const inhibitorPlaces = transition.inhibitors.map((arc) => placeIndex.get(arc.place.name)).filter((idx) => idx !== void 0);
|
|
261
|
+
const readPlaces = transition.reads.map((arc) => placeIndex.get(arc.place.name)).filter((idx) => idx !== void 0);
|
|
262
|
+
const resetPlaces = transition.resets.map((arc) => placeIndex.get(arc.place.name)).filter((idx) => idx !== void 0);
|
|
263
|
+
flatTransitions.push(flatTransition(
|
|
264
|
+
name,
|
|
265
|
+
transition,
|
|
266
|
+
branches.length > 1 ? branchIdx : -1,
|
|
267
|
+
preVector,
|
|
268
|
+
postVector,
|
|
269
|
+
inhibitorPlaces,
|
|
270
|
+
readPlaces,
|
|
271
|
+
resetPlaces,
|
|
272
|
+
consumeAll
|
|
273
|
+
));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
places,
|
|
278
|
+
placeIndex,
|
|
279
|
+
transitions: flatTransitions,
|
|
280
|
+
environmentBounds
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function enumerateOutputBranches(t) {
|
|
284
|
+
if (t.outputSpec !== null) {
|
|
285
|
+
return enumerateBranches(t.outputSpec);
|
|
286
|
+
}
|
|
287
|
+
return [/* @__PURE__ */ new Set()];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/verification/invariant/p-invariant.ts
|
|
291
|
+
function pInvariant(weights, constant, support) {
|
|
292
|
+
return { weights, constant, support };
|
|
293
|
+
}
|
|
294
|
+
function pInvariantToString(inv) {
|
|
295
|
+
const parts = [];
|
|
296
|
+
for (const i of inv.support) {
|
|
297
|
+
if (inv.weights[i] !== 1) {
|
|
298
|
+
parts.push(`${inv.weights[i]}*p${i}`);
|
|
299
|
+
} else {
|
|
300
|
+
parts.push(`p${i}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return `PInvariant[${parts.join(" + ")} = ${inv.constant}]`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/verification/invariant/p-invariant-computer.ts
|
|
307
|
+
function computePInvariants(matrix, flatNet, initialMarking) {
|
|
308
|
+
const P = matrix.numPlaces();
|
|
309
|
+
const T = matrix.numTransitions();
|
|
310
|
+
if (P === 0 || T === 0) return [];
|
|
311
|
+
const ct = matrix.transposedIncidence();
|
|
312
|
+
const cols = T + P;
|
|
313
|
+
const augmented = [];
|
|
314
|
+
for (let i = 0; i < P; i++) {
|
|
315
|
+
const row = new Array(cols).fill(0);
|
|
316
|
+
for (let j = 0; j < T; j++) {
|
|
317
|
+
row[j] = ct[i][j];
|
|
318
|
+
}
|
|
319
|
+
row[T + i] = 1;
|
|
320
|
+
augmented.push(row);
|
|
321
|
+
}
|
|
322
|
+
let pivotRow = 0;
|
|
323
|
+
for (let col = 0; col < T && pivotRow < P; col++) {
|
|
324
|
+
let pivot = -1;
|
|
325
|
+
for (let row = pivotRow; row < P; row++) {
|
|
326
|
+
if (augmented[row][col] !== 0) {
|
|
327
|
+
pivot = row;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (pivot === -1) continue;
|
|
332
|
+
if (pivot !== pivotRow) {
|
|
333
|
+
const tmp = augmented[pivotRow];
|
|
334
|
+
augmented[pivotRow] = augmented[pivot];
|
|
335
|
+
augmented[pivot] = tmp;
|
|
336
|
+
}
|
|
337
|
+
for (let row = 0; row < P; row++) {
|
|
338
|
+
if (row === pivotRow || augmented[row][col] === 0) continue;
|
|
339
|
+
const a = augmented[pivotRow][col];
|
|
340
|
+
const b = augmented[row][col];
|
|
341
|
+
for (let c = 0; c < cols; c++) {
|
|
342
|
+
augmented[row][c] = a * augmented[row][c] - b * augmented[pivotRow][c];
|
|
343
|
+
}
|
|
344
|
+
normalizeRow(augmented[row], cols);
|
|
345
|
+
}
|
|
346
|
+
pivotRow++;
|
|
347
|
+
}
|
|
348
|
+
const invariants = [];
|
|
349
|
+
for (let row = 0; row < P; row++) {
|
|
350
|
+
let isZero = true;
|
|
351
|
+
for (let col = 0; col < T; col++) {
|
|
352
|
+
if (augmented[row][col] !== 0) {
|
|
353
|
+
isZero = false;
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (!isZero) continue;
|
|
358
|
+
const weights = new Array(P);
|
|
359
|
+
let allNonNegative = true;
|
|
360
|
+
let hasPositive = false;
|
|
361
|
+
for (let i = 0; i < P; i++) {
|
|
362
|
+
weights[i] = augmented[row][T + i];
|
|
363
|
+
if (weights[i] < 0) {
|
|
364
|
+
allNonNegative = false;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
if (weights[i] > 0) hasPositive = true;
|
|
368
|
+
}
|
|
369
|
+
if (!allNonNegative) {
|
|
370
|
+
let allNonPositive = true;
|
|
371
|
+
for (let i = 0; i < P; i++) {
|
|
372
|
+
if (augmented[row][T + i] > 0) {
|
|
373
|
+
allNonPositive = false;
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (allNonPositive) {
|
|
378
|
+
for (let i = 0; i < P; i++) {
|
|
379
|
+
weights[i] = -augmented[row][T + i];
|
|
380
|
+
}
|
|
381
|
+
hasPositive = true;
|
|
382
|
+
allNonNegative = true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (!allNonNegative || !hasPositive) continue;
|
|
386
|
+
let g = 0;
|
|
387
|
+
for (const w of weights) {
|
|
388
|
+
if (w > 0) g = gcd(g, w);
|
|
389
|
+
}
|
|
390
|
+
if (g > 1) {
|
|
391
|
+
for (let i = 0; i < P; i++) {
|
|
392
|
+
weights[i] = weights[i] / g;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const support = /* @__PURE__ */ new Set();
|
|
396
|
+
let constant = 0;
|
|
397
|
+
for (let i = 0; i < P; i++) {
|
|
398
|
+
if (weights[i] !== 0) {
|
|
399
|
+
support.add(i);
|
|
400
|
+
const place = flatNet.places[i];
|
|
401
|
+
constant += weights[i] * initialMarking.tokens(place);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
invariants.push(pInvariant(weights, constant, support));
|
|
405
|
+
}
|
|
406
|
+
return invariants;
|
|
407
|
+
}
|
|
408
|
+
function isCoveredByInvariants(invariants, numPlaces) {
|
|
409
|
+
const covered = new Array(numPlaces).fill(false);
|
|
410
|
+
for (const inv of invariants) {
|
|
411
|
+
for (const idx of inv.support) {
|
|
412
|
+
if (idx < numPlaces) covered[idx] = true;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return covered.every((c) => c);
|
|
416
|
+
}
|
|
417
|
+
function normalizeRow(row, cols) {
|
|
418
|
+
let g = 0;
|
|
419
|
+
for (let c = 0; c < cols; c++) {
|
|
420
|
+
if (row[c] !== 0) {
|
|
421
|
+
g = gcd(g, Math.abs(row[c]));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (g > 1) {
|
|
425
|
+
for (let c = 0; c < cols; c++) {
|
|
426
|
+
row[c] = row[c] / g;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function gcd(a, b) {
|
|
431
|
+
while (b !== 0) {
|
|
432
|
+
const t = b;
|
|
433
|
+
b = a % b;
|
|
434
|
+
a = t;
|
|
435
|
+
}
|
|
436
|
+
return a;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/verification/invariant/structural-check.ts
|
|
440
|
+
var MAX_PLACES_FOR_SIPHON_ANALYSIS = 50;
|
|
441
|
+
function structuralCheck(flatNet, initialMarking) {
|
|
442
|
+
const P = flatNet.places.length;
|
|
443
|
+
if (P === 0) {
|
|
444
|
+
return { type: "no-potential-deadlock" };
|
|
445
|
+
}
|
|
446
|
+
if (P > MAX_PLACES_FOR_SIPHON_ANALYSIS) {
|
|
447
|
+
return { type: "inconclusive", reason: `Net has ${P} places, siphon enumeration skipped` };
|
|
448
|
+
}
|
|
449
|
+
const siphons = findMinimalSiphons(flatNet);
|
|
450
|
+
if (siphons.length === 0) {
|
|
451
|
+
return { type: "no-potential-deadlock" };
|
|
452
|
+
}
|
|
453
|
+
for (const siphon of siphons) {
|
|
454
|
+
const trap = findMaximalTrapIn(flatNet, siphon);
|
|
455
|
+
if (trap.size === 0 || !isMarked(trap, flatNet, initialMarking)) {
|
|
456
|
+
return { type: "potential-deadlock", siphon };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return { type: "no-potential-deadlock" };
|
|
460
|
+
}
|
|
461
|
+
function findMinimalSiphons(flatNet) {
|
|
462
|
+
const P = flatNet.places.length;
|
|
463
|
+
const siphons = [];
|
|
464
|
+
const placeAsOutput = [];
|
|
465
|
+
for (let p = 0; p < P; p++) {
|
|
466
|
+
placeAsOutput.push([]);
|
|
467
|
+
}
|
|
468
|
+
for (let t = 0; t < flatNet.transitions.length; t++) {
|
|
469
|
+
const ft = flatNet.transitions[t];
|
|
470
|
+
for (let p = 0; p < P; p++) {
|
|
471
|
+
if (ft.postVector[p] > 0) {
|
|
472
|
+
placeAsOutput[p].push(t);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
for (let startPlace = 0; startPlace < P; startPlace++) {
|
|
477
|
+
const siphon = computeSiphonContaining(startPlace, flatNet, placeAsOutput);
|
|
478
|
+
if (siphon !== null && siphon.size > 0) {
|
|
479
|
+
let isMinimal = true;
|
|
480
|
+
const toRemove = [];
|
|
481
|
+
for (let i = 0; i < siphons.length; i++) {
|
|
482
|
+
const existing = siphons[i];
|
|
483
|
+
if (setsEqual(existing, siphon)) {
|
|
484
|
+
isMinimal = false;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
if (isSubsetOf(existing, siphon)) {
|
|
488
|
+
isMinimal = false;
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
if (isSubsetOf(siphon, existing)) {
|
|
492
|
+
toRemove.push(i);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
for (let i = toRemove.length - 1; i >= 0; i--) {
|
|
496
|
+
siphons.splice(toRemove[i], 1);
|
|
497
|
+
}
|
|
498
|
+
if (isMinimal) {
|
|
499
|
+
siphons.push(siphon);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return siphons;
|
|
504
|
+
}
|
|
505
|
+
function computeSiphonContaining(startPlace, flatNet, placeAsOutput) {
|
|
506
|
+
const siphon = /* @__PURE__ */ new Set();
|
|
507
|
+
siphon.add(startPlace);
|
|
508
|
+
let changed = true;
|
|
509
|
+
while (changed) {
|
|
510
|
+
changed = false;
|
|
511
|
+
const snapshot = [...siphon];
|
|
512
|
+
for (const p of snapshot) {
|
|
513
|
+
for (const t of placeAsOutput[p]) {
|
|
514
|
+
const ft = flatNet.transitions[t];
|
|
515
|
+
let hasInputInSiphon = false;
|
|
516
|
+
for (let q = 0; q < flatNet.places.length; q++) {
|
|
517
|
+
if (ft.preVector[q] > 0 && siphon.has(q)) {
|
|
518
|
+
hasInputInSiphon = true;
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (!hasInputInSiphon) {
|
|
523
|
+
let added = false;
|
|
524
|
+
for (let q = 0; q < flatNet.places.length; q++) {
|
|
525
|
+
if (ft.preVector[q] > 0) {
|
|
526
|
+
if (!siphon.has(q)) {
|
|
527
|
+
siphon.add(q);
|
|
528
|
+
changed = true;
|
|
529
|
+
}
|
|
530
|
+
added = true;
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (!added) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return siphon;
|
|
542
|
+
}
|
|
543
|
+
function findMaximalTrapIn(flatNet, places) {
|
|
544
|
+
const trap = new Set(places);
|
|
545
|
+
let changed = true;
|
|
546
|
+
while (changed) {
|
|
547
|
+
changed = false;
|
|
548
|
+
const toRemove = [];
|
|
549
|
+
for (const p of trap) {
|
|
550
|
+
let satisfies = true;
|
|
551
|
+
for (let t = 0; t < flatNet.transitions.length; t++) {
|
|
552
|
+
const ft = flatNet.transitions[t];
|
|
553
|
+
if (ft.preVector[p] > 0) {
|
|
554
|
+
let outputsToTrap = false;
|
|
555
|
+
for (const q of trap) {
|
|
556
|
+
if (ft.postVector[q] > 0) {
|
|
557
|
+
outputsToTrap = true;
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (!outputsToTrap) {
|
|
562
|
+
satisfies = false;
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (!satisfies) {
|
|
568
|
+
toRemove.push(p);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (toRemove.length > 0) {
|
|
572
|
+
for (const p of toRemove) trap.delete(p);
|
|
573
|
+
changed = true;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return trap;
|
|
577
|
+
}
|
|
578
|
+
function isMarked(placeIndices, flatNet, marking) {
|
|
579
|
+
for (const idx of placeIndices) {
|
|
580
|
+
const place = flatNet.places[idx];
|
|
581
|
+
if (marking.tokens(place) > 0) return true;
|
|
582
|
+
}
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
function setsEqual(a, b) {
|
|
586
|
+
if (a.size !== b.size) return false;
|
|
587
|
+
for (const v of a) {
|
|
588
|
+
if (!b.has(v)) return false;
|
|
589
|
+
}
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
function isSubsetOf(sub, sup) {
|
|
593
|
+
if (sub.size > sup.size) return false;
|
|
594
|
+
for (const v of sub) {
|
|
595
|
+
if (!sup.has(v)) return false;
|
|
596
|
+
}
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/verification/smt-property.ts
|
|
601
|
+
function deadlockFree() {
|
|
602
|
+
return { type: "deadlock-free" };
|
|
603
|
+
}
|
|
604
|
+
function mutualExclusion(p1, p2) {
|
|
605
|
+
return { type: "mutual-exclusion", p1, p2 };
|
|
606
|
+
}
|
|
607
|
+
function placeBound(place, bound) {
|
|
608
|
+
return { type: "place-bound", place, bound };
|
|
609
|
+
}
|
|
610
|
+
function unreachable(places) {
|
|
611
|
+
return { type: "unreachable", places: new Set(places) };
|
|
612
|
+
}
|
|
613
|
+
function propertyDescription(prop) {
|
|
614
|
+
switch (prop.type) {
|
|
615
|
+
case "deadlock-free":
|
|
616
|
+
return "Deadlock-freedom";
|
|
617
|
+
case "mutual-exclusion":
|
|
618
|
+
return `Mutual exclusion of ${prop.p1.name} and ${prop.p2.name}`;
|
|
619
|
+
case "place-bound":
|
|
620
|
+
return `Place ${prop.place.name} bounded by ${prop.bound}`;
|
|
621
|
+
case "unreachable":
|
|
622
|
+
return `Unreachability of marking with tokens in {${[...prop.places].map((p) => p.name).join(", ")}}`;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/verification/smt-verification-result.ts
|
|
627
|
+
function isProven(result) {
|
|
628
|
+
return result.verdict.type === "proven";
|
|
629
|
+
}
|
|
630
|
+
function isViolated(result) {
|
|
631
|
+
return result.verdict.type === "violated";
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/verification/z3/spacer-runner.ts
|
|
635
|
+
import { init } from "z3-solver";
|
|
636
|
+
async function createSpacerRunner(timeoutMs) {
|
|
637
|
+
const { Context } = await init();
|
|
638
|
+
const ctx = new Context("main");
|
|
639
|
+
const fp = new ctx.Fixedpoint();
|
|
640
|
+
fp.set("engine", "spacer");
|
|
641
|
+
fp.set("spacer.global", true);
|
|
642
|
+
fp.set("spacer.use_bg_invs", true);
|
|
643
|
+
if (timeoutMs > 0) {
|
|
644
|
+
fp.set("timeout", Math.min(timeoutMs, 2147483647));
|
|
645
|
+
}
|
|
646
|
+
async function query(errorExpr, reachableDecl) {
|
|
647
|
+
try {
|
|
648
|
+
const status = await fp.query(errorExpr);
|
|
649
|
+
if (status === "unsat") {
|
|
650
|
+
let invariantFormula = null;
|
|
651
|
+
const levelInvariants = [];
|
|
652
|
+
try {
|
|
653
|
+
const answer = fp.getAnswer();
|
|
654
|
+
if (answer != null) {
|
|
655
|
+
invariantFormula = answer.toString();
|
|
656
|
+
}
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
659
|
+
if (reachableDecl != null) {
|
|
660
|
+
try {
|
|
661
|
+
const levels = fp.getNumLevels(reachableDecl);
|
|
662
|
+
for (let i = 0; i < levels; i++) {
|
|
663
|
+
const cover = fp.getCoverDelta(i, reachableDecl);
|
|
664
|
+
if (cover != null && !ctx.isTrue(cover)) {
|
|
665
|
+
levelInvariants.push(`Level ${i}: ${cover.toString()}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
} catch {
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return { type: "proven", invariantFormula, levelInvariants };
|
|
672
|
+
}
|
|
673
|
+
if (status === "sat") {
|
|
674
|
+
let answer = null;
|
|
675
|
+
try {
|
|
676
|
+
answer = fp.getAnswer();
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
679
|
+
return { type: "violated", answer };
|
|
680
|
+
}
|
|
681
|
+
return { type: "unknown", reason: fp.getReasonUnknown() };
|
|
682
|
+
} catch (e) {
|
|
683
|
+
return { type: "unknown", reason: `Z3 exception: ${e.message ?? e}` };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function dispose() {
|
|
687
|
+
try {
|
|
688
|
+
fp.release();
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
ctx,
|
|
694
|
+
fp,
|
|
695
|
+
query,
|
|
696
|
+
dispose
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// src/verification/z3/smt-encoder.ts
|
|
701
|
+
function encode(ctx, fp, flatNet, initialMarking, property, invariants) {
|
|
702
|
+
const P = flatNet.places.length;
|
|
703
|
+
const Int = ctx.Int;
|
|
704
|
+
const Bool_ = ctx.Bool;
|
|
705
|
+
const intSort = Int.sort();
|
|
706
|
+
const boolSort = Bool_.sort();
|
|
707
|
+
const markingSorts = new Array(P).fill(intSort);
|
|
708
|
+
const reachable = ctx.Function.declare("Reachable", ...markingSorts, boolSort);
|
|
709
|
+
fp.registerRelation(reachable);
|
|
710
|
+
const error = ctx.Function.declare("Error", boolSort);
|
|
711
|
+
fp.registerRelation(error);
|
|
712
|
+
const m0Args = [];
|
|
713
|
+
for (let i = 0; i < P; i++) {
|
|
714
|
+
const tokens = initialMarking.tokens(flatNet.places[i]);
|
|
715
|
+
m0Args.push(Int.val(tokens));
|
|
716
|
+
}
|
|
717
|
+
const initFact = reachable.call(...m0Args);
|
|
718
|
+
fp.addRule(initFact, "init");
|
|
719
|
+
for (let t = 0; t < flatNet.transitions.length; t++) {
|
|
720
|
+
const ft = flatNet.transitions[t];
|
|
721
|
+
encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P);
|
|
722
|
+
}
|
|
723
|
+
encodeErrorRule(ctx, fp, reachable, error, flatNet, property, P);
|
|
724
|
+
return {
|
|
725
|
+
errorExpr: error.call(),
|
|
726
|
+
reachableDecl: reachable
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function encodeTransitionRule(ctx, fp, reachable, ft, flatNet, invariants, P) {
|
|
730
|
+
const Int = ctx.Int;
|
|
731
|
+
const mVars = [];
|
|
732
|
+
const mPrimeVars = [];
|
|
733
|
+
for (let i = 0; i < P; i++) {
|
|
734
|
+
mVars.push(Int.const(`m${i}`));
|
|
735
|
+
mPrimeVars.push(Int.const(`mp${i}`));
|
|
736
|
+
}
|
|
737
|
+
const reachBody = reachable.call(...mVars);
|
|
738
|
+
const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
|
|
739
|
+
const fireRelation = encodeFire(ctx, ft, flatNet, mVars, mPrimeVars, P);
|
|
740
|
+
let nonNeg = ctx.Bool.val(true);
|
|
741
|
+
for (let i = 0; i < P; i++) {
|
|
742
|
+
nonNeg = ctx.And(nonNeg, mPrimeVars[i].ge(0));
|
|
743
|
+
}
|
|
744
|
+
const invConstraints = encodeInvariantConstraints(ctx, invariants, mPrimeVars, P);
|
|
745
|
+
let envBounds = ctx.Bool.val(true);
|
|
746
|
+
for (const [name, bound] of flatNet.environmentBounds) {
|
|
747
|
+
const idx = flatNet.placeIndex.get(name);
|
|
748
|
+
if (idx != null) {
|
|
749
|
+
envBounds = ctx.And(envBounds, mPrimeVars[idx].le(bound));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const body = ctx.And(reachBody, enabled, fireRelation, nonNeg, invConstraints, envBounds);
|
|
753
|
+
const head = reachable.call(...mPrimeVars);
|
|
754
|
+
const allVars = [...mVars, ...mPrimeVars];
|
|
755
|
+
const rule = ctx.Implies(body, head);
|
|
756
|
+
const qRule = ctx.ForAll(allVars, rule);
|
|
757
|
+
fp.addRule(qRule, `t_${ft.name}`);
|
|
758
|
+
}
|
|
759
|
+
function encodeEnabled(ctx, ft, _flatNet, mVars, P) {
|
|
760
|
+
let result = ctx.Bool.val(true);
|
|
761
|
+
for (let p = 0; p < P; p++) {
|
|
762
|
+
if (ft.preVector[p] > 0) {
|
|
763
|
+
result = ctx.And(result, mVars[p].ge(ft.preVector[p]));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
for (const p of ft.readPlaces) {
|
|
767
|
+
result = ctx.And(result, mVars[p].ge(1));
|
|
768
|
+
}
|
|
769
|
+
for (const p of ft.inhibitorPlaces) {
|
|
770
|
+
result = ctx.And(result, mVars[p].eq(0));
|
|
771
|
+
}
|
|
772
|
+
for (let p = 0; p < P; p++) {
|
|
773
|
+
result = ctx.And(result, mVars[p].ge(0));
|
|
774
|
+
}
|
|
775
|
+
return result;
|
|
776
|
+
}
|
|
777
|
+
function encodeFire(ctx, ft, _flatNet, mVars, mPrimeVars, P) {
|
|
778
|
+
let result = ctx.Bool.val(true);
|
|
779
|
+
for (let p = 0; p < P; p++) {
|
|
780
|
+
const isReset = ft.resetPlaces.includes(p);
|
|
781
|
+
if (isReset || ft.consumeAll[p]) {
|
|
782
|
+
result = ctx.And(result, mPrimeVars[p].eq(ft.postVector[p]));
|
|
783
|
+
} else {
|
|
784
|
+
const delta = ft.postVector[p] - ft.preVector[p];
|
|
785
|
+
if (delta === 0) {
|
|
786
|
+
result = ctx.And(result, mPrimeVars[p].eq(mVars[p]));
|
|
787
|
+
} else {
|
|
788
|
+
result = ctx.And(result, mPrimeVars[p].eq(mVars[p].add(delta)));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
function encodeErrorRule(ctx, fp, reachable, error, flatNet, property, P) {
|
|
795
|
+
const Int = ctx.Int;
|
|
796
|
+
const mVars = [];
|
|
797
|
+
for (let i = 0; i < P; i++) {
|
|
798
|
+
mVars.push(Int.const(`em${i}`));
|
|
799
|
+
}
|
|
800
|
+
const reachBody = reachable.call(...mVars);
|
|
801
|
+
const violation = encodePropertyViolation(ctx, flatNet, property, mVars, P);
|
|
802
|
+
const head = error.call();
|
|
803
|
+
const body = ctx.And(reachBody, violation);
|
|
804
|
+
const rule = ctx.Implies(body, head);
|
|
805
|
+
const qRule = ctx.ForAll(mVars, rule);
|
|
806
|
+
fp.addRule(qRule, `error_${property.type}`);
|
|
807
|
+
}
|
|
808
|
+
function encodePropertyViolation(ctx, flatNet, property, mVars, P) {
|
|
809
|
+
switch (property.type) {
|
|
810
|
+
case "deadlock-free":
|
|
811
|
+
return encodeDeadlock(ctx, flatNet, mVars, P);
|
|
812
|
+
case "mutual-exclusion": {
|
|
813
|
+
const idx1 = flatNetIndexOf(flatNet, property.p1);
|
|
814
|
+
const idx2 = flatNetIndexOf(flatNet, property.p2);
|
|
815
|
+
if (idx1 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p1.name}`);
|
|
816
|
+
if (idx2 < 0) throw new Error(`MutualExclusion references unknown place: ${property.p2.name}`);
|
|
817
|
+
return ctx.And(mVars[idx1].ge(1), mVars[idx2].ge(1));
|
|
818
|
+
}
|
|
819
|
+
case "place-bound": {
|
|
820
|
+
const idx = flatNetIndexOf(flatNet, property.place);
|
|
821
|
+
if (idx < 0) throw new Error(`PlaceBound references unknown place: ${property.place.name}`);
|
|
822
|
+
return mVars[idx].gt(property.bound);
|
|
823
|
+
}
|
|
824
|
+
case "unreachable": {
|
|
825
|
+
let allMarked = ctx.Bool.val(true);
|
|
826
|
+
for (const place of property.places) {
|
|
827
|
+
const idx = flatNetIndexOf(flatNet, place);
|
|
828
|
+
if (idx >= 0) {
|
|
829
|
+
allMarked = ctx.And(allMarked, mVars[idx].ge(1));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return allMarked;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
function encodeDeadlock(ctx, flatNet, mVars, P) {
|
|
837
|
+
let deadlock = ctx.Bool.val(true);
|
|
838
|
+
for (const ft of flatNet.transitions) {
|
|
839
|
+
const enabled = encodeEnabled(ctx, ft, flatNet, mVars, P);
|
|
840
|
+
deadlock = ctx.And(deadlock, ctx.Not(enabled));
|
|
841
|
+
}
|
|
842
|
+
return deadlock;
|
|
843
|
+
}
|
|
844
|
+
function encodeInvariantConstraints(ctx, invariants, mVars, P) {
|
|
845
|
+
let result = ctx.Bool.val(true);
|
|
846
|
+
for (const inv of invariants) {
|
|
847
|
+
let sum = ctx.Int.val(0);
|
|
848
|
+
for (const idx of inv.support) {
|
|
849
|
+
if (idx < P) {
|
|
850
|
+
sum = sum.add(mVars[idx].mul(inv.weights[idx]));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
result = ctx.And(result, sum.eq(inv.constant));
|
|
854
|
+
}
|
|
855
|
+
return result;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/verification/z3/counterexample-decoder.ts
|
|
859
|
+
function decode(ctx, answer, flatNet) {
|
|
860
|
+
const trace = [];
|
|
861
|
+
const transitions = [];
|
|
862
|
+
if (answer == null) {
|
|
863
|
+
return { trace, transitions };
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
extractTrace(ctx, answer, flatNet, trace, transitions);
|
|
867
|
+
} catch {
|
|
868
|
+
}
|
|
869
|
+
return { trace, transitions };
|
|
870
|
+
}
|
|
871
|
+
function extractTrace(ctx, expr, flatNet, trace, transitions) {
|
|
872
|
+
if (expr == null) return;
|
|
873
|
+
if (!ctx.isApp(expr)) return;
|
|
874
|
+
let name;
|
|
875
|
+
try {
|
|
876
|
+
const decl = expr.decl();
|
|
877
|
+
name = String(decl.name());
|
|
878
|
+
} catch {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const P = flatNet.places.length;
|
|
882
|
+
if (name === "Reachable") {
|
|
883
|
+
const numArgs = expr.numArgs();
|
|
884
|
+
if (numArgs === P) {
|
|
885
|
+
const marking = extractMarking(ctx, expr, flatNet);
|
|
886
|
+
if (marking != null) {
|
|
887
|
+
trace.push(marking);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
try {
|
|
892
|
+
const numArgs = expr.numArgs();
|
|
893
|
+
for (let i = 0; i < numArgs; i++) {
|
|
894
|
+
const child = expr.arg(i);
|
|
895
|
+
extractTrace(ctx, child, flatNet, trace, transitions);
|
|
896
|
+
}
|
|
897
|
+
} catch {
|
|
898
|
+
}
|
|
899
|
+
if (name.startsWith("t_")) {
|
|
900
|
+
transitions.push(name.substring(2));
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function extractMarking(ctx, reachableApp, flatNet) {
|
|
904
|
+
const P = flatNet.places.length;
|
|
905
|
+
if (reachableApp.numArgs() !== P) return null;
|
|
906
|
+
const builder = MarkingState.builder();
|
|
907
|
+
for (let i = 0; i < P; i++) {
|
|
908
|
+
const arg = reachableApp.arg(i);
|
|
909
|
+
if (ctx.isIntVal(arg)) {
|
|
910
|
+
const tokens = Number(arg.value());
|
|
911
|
+
if (tokens > 0) {
|
|
912
|
+
builder.tokens(flatNet.places[i], tokens);
|
|
913
|
+
}
|
|
914
|
+
} else {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return builder.build();
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/verification/smt-verifier.ts
|
|
922
|
+
var SmtVerifier = class _SmtVerifier {
|
|
923
|
+
constructor(net) {
|
|
924
|
+
this.net = net;
|
|
925
|
+
}
|
|
926
|
+
_initialMarking = MarkingState.empty();
|
|
927
|
+
_property = deadlockFree();
|
|
928
|
+
_environmentPlaces = /* @__PURE__ */ new Set();
|
|
929
|
+
_environmentMode = unbounded();
|
|
930
|
+
_timeoutMs = 6e4;
|
|
931
|
+
static forNet(net) {
|
|
932
|
+
return new _SmtVerifier(net);
|
|
933
|
+
}
|
|
934
|
+
initialMarking(arg) {
|
|
935
|
+
if (arg instanceof MarkingState) {
|
|
936
|
+
this._initialMarking = arg;
|
|
937
|
+
} else {
|
|
938
|
+
const builder = MarkingState.builder();
|
|
939
|
+
arg(builder);
|
|
940
|
+
this._initialMarking = builder.build();
|
|
941
|
+
}
|
|
942
|
+
return this;
|
|
943
|
+
}
|
|
944
|
+
property(property) {
|
|
945
|
+
this._property = property;
|
|
946
|
+
return this;
|
|
947
|
+
}
|
|
948
|
+
environmentPlaces(...places) {
|
|
949
|
+
for (const p of places) this._environmentPlaces.add(p);
|
|
950
|
+
return this;
|
|
951
|
+
}
|
|
952
|
+
environmentMode(mode) {
|
|
953
|
+
this._environmentMode = mode;
|
|
954
|
+
return this;
|
|
955
|
+
}
|
|
956
|
+
timeout(ms) {
|
|
957
|
+
this._timeoutMs = ms;
|
|
958
|
+
return this;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Runs the verification pipeline.
|
|
962
|
+
*/
|
|
963
|
+
async verify() {
|
|
964
|
+
const start = performance.now();
|
|
965
|
+
const report = [];
|
|
966
|
+
report.push("=== IC3/PDR SAFETY VERIFICATION ===\n");
|
|
967
|
+
report.push(`Net: ${this.net.name}`);
|
|
968
|
+
report.push(`Property: ${propertyDescription(this._property)}`);
|
|
969
|
+
report.push(`Timeout: ${(this._timeoutMs / 1e3).toFixed(0)}s
|
|
970
|
+
`);
|
|
971
|
+
report.push("Phase 1: Flattening net...");
|
|
972
|
+
const flatNet = flatten(this.net, this._environmentPlaces, this._environmentMode);
|
|
973
|
+
report.push(` Places: ${flatNet.places.length}`);
|
|
974
|
+
report.push(` Transitions (expanded): ${flatNet.transitions.length}`);
|
|
975
|
+
if (flatNet.environmentBounds.size > 0) {
|
|
976
|
+
report.push(` Environment bounds: ${flatNet.environmentBounds.size} places`);
|
|
977
|
+
}
|
|
978
|
+
report.push("");
|
|
979
|
+
report.push("Phase 2: Structural pre-check (siphon/trap)...");
|
|
980
|
+
const structResult = structuralCheck(flatNet, this._initialMarking);
|
|
981
|
+
let structResultStr;
|
|
982
|
+
switch (structResult.type) {
|
|
983
|
+
case "no-potential-deadlock":
|
|
984
|
+
structResultStr = "no potential deadlock";
|
|
985
|
+
break;
|
|
986
|
+
case "potential-deadlock":
|
|
987
|
+
structResultStr = `potential deadlock (siphon: {${[...structResult.siphon].join(",")}})`;
|
|
988
|
+
break;
|
|
989
|
+
case "inconclusive":
|
|
990
|
+
structResultStr = `inconclusive (${structResult.reason})`;
|
|
991
|
+
break;
|
|
992
|
+
}
|
|
993
|
+
report.push(` Result: ${structResultStr}
|
|
994
|
+
`);
|
|
995
|
+
if (this._property.type === "deadlock-free" && structResult.type === "no-potential-deadlock") {
|
|
996
|
+
report.push("=== RESULT ===\n");
|
|
997
|
+
report.push("PROVEN (structural): Deadlock-freedom verified by Commoner's theorem.");
|
|
998
|
+
report.push(" All siphons contain initially marked traps.");
|
|
999
|
+
return buildResult(
|
|
1000
|
+
{ type: "proven", method: "structural", inductiveInvariant: null },
|
|
1001
|
+
report.join("\n"),
|
|
1002
|
+
[],
|
|
1003
|
+
[],
|
|
1004
|
+
[],
|
|
1005
|
+
[],
|
|
1006
|
+
performance.now() - start,
|
|
1007
|
+
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: 0, structuralResult: structResultStr }
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
report.push("Phase 3: Computing P-invariants...");
|
|
1011
|
+
const matrix = IncidenceMatrix.from(flatNet);
|
|
1012
|
+
const invariants = computePInvariants(matrix, flatNet, this._initialMarking);
|
|
1013
|
+
report.push(` Found: ${invariants.length} P-invariant(s)`);
|
|
1014
|
+
const structurallyBounded = isCoveredByInvariants(invariants, flatNet.places.length);
|
|
1015
|
+
report.push(` Structurally bounded: ${structurallyBounded ? "YES" : "NO"}`);
|
|
1016
|
+
for (const inv of invariants) {
|
|
1017
|
+
report.push(` ${formatInvariant(inv, flatNet)}`);
|
|
1018
|
+
}
|
|
1019
|
+
report.push("");
|
|
1020
|
+
report.push("Phase 4: IC3/PDR verification via Z3 Spacer...");
|
|
1021
|
+
let runner;
|
|
1022
|
+
try {
|
|
1023
|
+
runner = await createSpacerRunner(this._timeoutMs);
|
|
1024
|
+
} catch (e) {
|
|
1025
|
+
report.push(` ERROR: ${e.message ?? e}
|
|
1026
|
+
`);
|
|
1027
|
+
report.push("=== RESULT ===\n");
|
|
1028
|
+
report.push(`UNKNOWN: Z3 initialization error: ${e.message ?? e}`);
|
|
1029
|
+
return buildResult(
|
|
1030
|
+
{ type: "unknown", reason: `Z3 init error: ${e.message ?? e}` },
|
|
1031
|
+
report.join("\n"),
|
|
1032
|
+
invariants,
|
|
1033
|
+
[],
|
|
1034
|
+
[],
|
|
1035
|
+
[],
|
|
1036
|
+
performance.now() - start,
|
|
1037
|
+
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
try {
|
|
1041
|
+
const encoding = encode(runner.ctx, runner.fp, flatNet, this._initialMarking, this._property, invariants);
|
|
1042
|
+
const queryResult = await runner.query(encoding.errorExpr, encoding.reachableDecl);
|
|
1043
|
+
switch (queryResult.type) {
|
|
1044
|
+
case "proven": {
|
|
1045
|
+
report.push(" Status: UNSAT (property holds)\n");
|
|
1046
|
+
const discoveredInvariants = [];
|
|
1047
|
+
if (queryResult.invariantFormula != null) {
|
|
1048
|
+
discoveredInvariants.push(substituteNames(queryResult.invariantFormula, flatNet));
|
|
1049
|
+
}
|
|
1050
|
+
for (const level of queryResult.levelInvariants) {
|
|
1051
|
+
discoveredInvariants.push(substituteNames(level, flatNet));
|
|
1052
|
+
}
|
|
1053
|
+
if (discoveredInvariants.length > 0) {
|
|
1054
|
+
report.push("Phase 5: Inductive invariant (discovered by IC3)");
|
|
1055
|
+
report.push(` Spacer synthesized: ${discoveredInvariants[0]}`);
|
|
1056
|
+
report.push(" This formula is INDUCTIVE: preserved by all transitions.");
|
|
1057
|
+
if (discoveredInvariants.length > 1) {
|
|
1058
|
+
report.push(" Per-level clauses:");
|
|
1059
|
+
for (let i = 1; i < discoveredInvariants.length; i++) {
|
|
1060
|
+
report.push(` ${discoveredInvariants[i]}`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
report.push("");
|
|
1064
|
+
}
|
|
1065
|
+
report.push("=== RESULT ===\n");
|
|
1066
|
+
report.push(`PROVEN (IC3/PDR): ${propertyDescription(this._property)}`);
|
|
1067
|
+
report.push(" Z3 Spacer proved no reachable state violates the property.");
|
|
1068
|
+
report.push(" NOTE: Verification ignores timing constraints and JS guards.");
|
|
1069
|
+
report.push(" An untimed proof is STRONGER than a timed one (timing only restricts behavior).");
|
|
1070
|
+
return buildResult(
|
|
1071
|
+
{
|
|
1072
|
+
type: "proven",
|
|
1073
|
+
method: "IC3/PDR",
|
|
1074
|
+
inductiveInvariant: queryResult.invariantFormula != null ? substituteNames(queryResult.invariantFormula, flatNet) : null
|
|
1075
|
+
},
|
|
1076
|
+
report.join("\n"),
|
|
1077
|
+
invariants,
|
|
1078
|
+
discoveredInvariants,
|
|
1079
|
+
[],
|
|
1080
|
+
[],
|
|
1081
|
+
performance.now() - start,
|
|
1082
|
+
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
case "violated": {
|
|
1086
|
+
report.push(" Status: SAT (counterexample found)\n");
|
|
1087
|
+
const decoded = decode(runner.ctx, queryResult.answer, flatNet);
|
|
1088
|
+
report.push("=== RESULT ===\n");
|
|
1089
|
+
report.push(`VIOLATED: ${propertyDescription(this._property)}`);
|
|
1090
|
+
if (decoded.trace.length > 0) {
|
|
1091
|
+
report.push(` Counterexample trace (${decoded.trace.length} states):`);
|
|
1092
|
+
for (let i = 0; i < decoded.trace.length; i++) {
|
|
1093
|
+
report.push(` ${i}: ${decoded.trace[i]}`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
if (decoded.transitions.length > 0) {
|
|
1097
|
+
report.push(` Firing sequence: ${decoded.transitions.join(" -> ")}`);
|
|
1098
|
+
}
|
|
1099
|
+
report.push("\n WARNING: This counterexample is in UNTIMED semantics.");
|
|
1100
|
+
report.push(" It may be spurious if timing constraints prevent this sequence.");
|
|
1101
|
+
report.push(" JS guards are also ignored in this analysis.");
|
|
1102
|
+
return buildResult(
|
|
1103
|
+
{ type: "violated" },
|
|
1104
|
+
report.join("\n"),
|
|
1105
|
+
invariants,
|
|
1106
|
+
[],
|
|
1107
|
+
decoded.trace,
|
|
1108
|
+
decoded.transitions,
|
|
1109
|
+
performance.now() - start,
|
|
1110
|
+
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
case "unknown": {
|
|
1114
|
+
report.push(` Status: UNKNOWN (${queryResult.reason})
|
|
1115
|
+
`);
|
|
1116
|
+
report.push("=== RESULT ===\n");
|
|
1117
|
+
report.push(`UNKNOWN: Could not determine ${propertyDescription(this._property)}`);
|
|
1118
|
+
report.push(` Reason: ${queryResult.reason}`);
|
|
1119
|
+
return buildResult(
|
|
1120
|
+
{ type: "unknown", reason: queryResult.reason },
|
|
1121
|
+
report.join("\n"),
|
|
1122
|
+
invariants,
|
|
1123
|
+
[],
|
|
1124
|
+
[],
|
|
1125
|
+
[],
|
|
1126
|
+
performance.now() - start,
|
|
1127
|
+
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
} catch (e) {
|
|
1132
|
+
report.push(` ERROR: ${e.message ?? e}
|
|
1133
|
+
`);
|
|
1134
|
+
report.push("=== RESULT ===\n");
|
|
1135
|
+
report.push(`UNKNOWN: Z3 solver error: ${e.message ?? e}`);
|
|
1136
|
+
return buildResult(
|
|
1137
|
+
{ type: "unknown", reason: `Z3 error: ${e.message ?? e}` },
|
|
1138
|
+
report.join("\n"),
|
|
1139
|
+
invariants,
|
|
1140
|
+
[],
|
|
1141
|
+
[],
|
|
1142
|
+
[],
|
|
1143
|
+
performance.now() - start,
|
|
1144
|
+
{ places: flatNet.places.length, transitions: flatNet.transitions.length, invariantsFound: invariants.length, structuralResult: structResultStr }
|
|
1145
|
+
);
|
|
1146
|
+
} finally {
|
|
1147
|
+
runner.dispose();
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
function substituteNames(formula, flatNet) {
|
|
1152
|
+
for (let i = flatNet.places.length - 1; i >= 0; i--) {
|
|
1153
|
+
formula = formula.replace(new RegExp(`\\bm${i}\\b`, "g"), flatNet.places[i].name);
|
|
1154
|
+
}
|
|
1155
|
+
return formula;
|
|
1156
|
+
}
|
|
1157
|
+
function formatInvariant(inv, flatNet) {
|
|
1158
|
+
const parts = [];
|
|
1159
|
+
for (const idx of inv.support) {
|
|
1160
|
+
if (inv.weights[idx] !== 1) {
|
|
1161
|
+
parts.push(`${inv.weights[idx]}*${flatNet.places[idx].name}`);
|
|
1162
|
+
} else {
|
|
1163
|
+
parts.push(flatNet.places[idx].name);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return `${parts.join(" + ")} = ${inv.constant}`;
|
|
1167
|
+
}
|
|
1168
|
+
function buildResult(verdict, report, invariants, discoveredInvariants, trace, transitions, elapsedMs, statistics) {
|
|
1169
|
+
return { verdict, report, invariants, discoveredInvariants, counterexampleTrace: trace, counterexampleTransitions: transitions, elapsedMs, statistics };
|
|
1170
|
+
}
|
|
1171
|
+
export {
|
|
1172
|
+
IncidenceMatrix,
|
|
1173
|
+
MarkingState,
|
|
1174
|
+
MarkingStateBuilder,
|
|
1175
|
+
SmtVerifier,
|
|
1176
|
+
bounded,
|
|
1177
|
+
computePInvariants,
|
|
1178
|
+
createSpacerRunner,
|
|
1179
|
+
deadlockFree,
|
|
1180
|
+
decode,
|
|
1181
|
+
encode,
|
|
1182
|
+
findMaximalTrapIn,
|
|
1183
|
+
findMinimalSiphons,
|
|
1184
|
+
flatNetIndexOf,
|
|
1185
|
+
flatNetPlaceCount,
|
|
1186
|
+
flatNetTransitionCount,
|
|
1187
|
+
flatTransition,
|
|
1188
|
+
flatten,
|
|
1189
|
+
isCoveredByInvariants,
|
|
1190
|
+
isProven,
|
|
1191
|
+
isViolated,
|
|
1192
|
+
mutualExclusion,
|
|
1193
|
+
pInvariant,
|
|
1194
|
+
pInvariantToString,
|
|
1195
|
+
placeBound,
|
|
1196
|
+
propertyDescription,
|
|
1197
|
+
structuralCheck,
|
|
1198
|
+
unbounded,
|
|
1199
|
+
unreachable
|
|
1200
|
+
};
|
|
1201
|
+
//# sourceMappingURL=index.js.map
|