gdp-wired 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -113
- package/dist/core/dsl.d.ts +210 -9
- package/dist/core/dsl.js +799 -78
- package/dist/core/watch.d.ts +14 -0
- package/dist/core/watch.js +78 -0
- package/dist/kernel/kernel.d.ts +7 -0
- package/dist/kernel/kernel.js +121 -11
- package/dist/kernel/prove.d.ts +3 -0
- package/dist/kernel/prove.js +190 -51
- package/dist/lean-kernel/emit.d.ts +2 -10
- package/dist/lean-kernel/emit.js +164 -201
- package/dist/lean-kernel/library.js +5 -5
- package/package.json +2 -2
package/dist/kernel/prove.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { add, k, scale, solve, sub } from "../core/solver.js";
|
|
2
|
-
import { canon, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
|
|
1
|
+
import { add, k, scale, solve, sub, v } from "../core/solver.js";
|
|
2
|
+
import { assumed, canon, columnTerm, decided, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
|
|
3
3
|
// The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
|
|
4
4
|
function openE(e, path, out) {
|
|
5
5
|
switch (e.op) {
|
|
@@ -22,7 +22,7 @@ function openE(e, path, out) {
|
|
|
22
22
|
case 'max': {
|
|
23
23
|
openE(e.a, path, out), openE(e.b, path, out);
|
|
24
24
|
const a = linear(e.a, path), b = linear(e.b, path);
|
|
25
|
-
if (a && b &&
|
|
25
|
+
if (a && b && !decided(e, path))
|
|
26
26
|
out.push(e.op === 'sub' ? sub(b, a) : sub(a, b));
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
@@ -116,12 +116,34 @@ function parse(text) {
|
|
|
116
116
|
// The budget: 200,000 nodes and, by default, a second per hypothesis (at least 20 seconds) per goal; past either the goal
|
|
117
117
|
// is left `unknown`. A goal with many instances (a law about two keys at its pairs) has that many more rows per leaf.
|
|
118
118
|
export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
|
|
119
|
+
// At the root: plainly first; where the cases multiply past a budget, through cuts. The plain budget halves with each
|
|
120
|
+
// two open choices (what a plain search's cases grow by), so a long run's steps reach the cuts early.
|
|
121
|
+
if (path.size === 0 && budget.nodes === 0) {
|
|
122
|
+
const open = new Set(leafConstraints(goal, path).flatMap(r => Object.keys(r.lin)).filter(n => n.startsWith('⟦') && columnTerm(n)?.op === 'if')).size;
|
|
123
|
+
const plain = growPlain(goal, path, { nodes: 0, until: budget.until, cap: Math.max(32, Math.round(4_000 / 2 ** (open / 2))) });
|
|
124
|
+
if ('tree' in plain || plain.stuck === 'sat' || plain.why === undefined)
|
|
125
|
+
return plain;
|
|
126
|
+
const cut = withCuts(goal, path, budget);
|
|
127
|
+
if (cut)
|
|
128
|
+
return cut;
|
|
129
|
+
}
|
|
130
|
+
return growPlain(goal, path, budget);
|
|
131
|
+
}
|
|
132
|
+
function growPlain(goal, path, budget) {
|
|
133
|
+
if (budget.cap !== undefined && budget.nodes >= budget.cap)
|
|
134
|
+
return { stuck: 'unknown', path, why: 'cap' };
|
|
119
135
|
if (++budget.nodes > 200_000 || (budget.until !== undefined && performance.now() > budget.until))
|
|
120
|
-
return { stuck: 'unknown', path };
|
|
121
|
-
|
|
136
|
+
return { stuck: 'unknown', path, why: outOfBudget(goal, budget.nodes > 200_000 ? 'nodes' : 'time', path.size) };
|
|
137
|
+
// Rows that read a term column only once a cut has put one on the path; before, the rows a choice-free search uses
|
|
138
|
+
// (what it saw before term columns), renumbered to the whole leaf's when a leaf is found.
|
|
139
|
+
const full = leafConstraints(goal, path);
|
|
140
|
+
const columns = [...path.values()].some(a => Object.keys(a.lin).some(key => key.startsWith('⟦')));
|
|
141
|
+
const idx = full.flatMap((r, i) => (columns || !Object.keys(r.lin).some(key => key.startsWith('⟦')) ? [i] : []));
|
|
142
|
+
const rows = idx.map(i => full[i]), at = (i) => (i < rows.length ? idx[i] : full.length + i - rows.length);
|
|
143
|
+
const leafOf = (steps, products) => ({ leaf: steps.map(st => (st.kind === 'input' ? { ...st, index: at(st.index) } : st)), ...(products ? { products: products.map(([i, j]) => [at(i), at(j)]) } : {}) });
|
|
122
144
|
const verdict = solve(rows);
|
|
123
145
|
if (verdict.kind === 'refuted')
|
|
124
|
-
return { tree:
|
|
146
|
+
return { tree: leafOf(verdict.steps), nodes: budget.nodes };
|
|
125
147
|
const open = [];
|
|
126
148
|
for (const h of goal.hyps)
|
|
127
149
|
openH(h, path, open);
|
|
@@ -148,7 +170,7 @@ export function grow(goal, path = new Map(), budget = { nodes: 0, until: perform
|
|
|
148
170
|
pairs.splice(300);
|
|
149
171
|
const product = solve(withProducts(rows, pairs));
|
|
150
172
|
if (product.kind === 'refuted')
|
|
151
|
-
return { tree:
|
|
173
|
+
return { tree: leafOf(product.steps, pairs), nodes: budget.nodes };
|
|
152
174
|
// Degree three: each row times each product of two variables (x·y >= 0 is the pair of -x <= 0 and -y <= 0).
|
|
153
175
|
const degree = Math.max(...rows.flatMap(r => Object.keys(r.lin).map(key => (key === '1' ? 0 : key.split('*').length))));
|
|
154
176
|
if (degree >= 3) {
|
|
@@ -159,7 +181,7 @@ export function grow(goal, path = new Map(), budget = { nodes: 0, until: perform
|
|
|
159
181
|
cubes.push([rows.length + s, i]);
|
|
160
182
|
const third = solve(withProducts(rows, [...sq, ...cubes]));
|
|
161
183
|
if (third.kind === 'refuted')
|
|
162
|
-
return { tree:
|
|
184
|
+
return { tree: leafOf(third.steps, [...sq, ...cubes]), nodes: budget.nodes };
|
|
163
185
|
}
|
|
164
186
|
const factors = [...new Set(rows.flatMap(r => Object.keys(r.lin).filter(key => key.includes('*')).flatMap(key => key.split('*'))))];
|
|
165
187
|
const fresh = (lin) => !path.has(canon(lin)) && !path.has(canon(add(k(1), scale(lin, -1))));
|
|
@@ -174,14 +196,105 @@ export function grow(goal, path = new Map(), budget = { nodes: 0, until: perform
|
|
|
174
196
|
if (!split)
|
|
175
197
|
return { stuck: verdict.kind === 'sat' ? 'sat' : 'unknown', ...(verdict.kind === 'sat' ? { witness: verdict.witness } : {}), path };
|
|
176
198
|
const id = canon(split);
|
|
177
|
-
const yes =
|
|
199
|
+
const yes = growPlain(goal, new Map(path).set(id, { lin: split, holds: true }), budget);
|
|
200
|
+
if (!('tree' in yes))
|
|
201
|
+
return yes;
|
|
202
|
+
const no = growPlain(goal, new Map(path).set(id, { lin: split, holds: false }), budget);
|
|
203
|
+
if (!('tree' in no))
|
|
204
|
+
return no;
|
|
205
|
+
return { tree: { split, yes: yes.tree, no: no.tree }, nodes: budget.nodes };
|
|
206
|
+
}
|
|
207
|
+
// What ran out, for a goal left unknown: its size, how deep the case splits went, and the two shapes that usually cause it.
|
|
208
|
+
function outOfBudget(goal, limit, depth) {
|
|
209
|
+
const steps = Number(/@(\d+) steps/.exec(goal.name)?.[1] ?? 1);
|
|
210
|
+
return `the prover's ${limit === 'nodes' ? '200,000-node' : 'time'} budget ran out at ${goal.hyps.length} hypotheses, ${depth} case splits deep. ` +
|
|
211
|
+
`Each case a step decides multiplies the cases to refute, so ${steps > 1 ? `a law over ${steps} steps` : 'many interacting cases'} can outgrow it: ` +
|
|
212
|
+
'state the law over fewer steps (a one-step law and an invariant often give the same), or split it into laws about fewer conditions';
|
|
213
|
+
}
|
|
214
|
+
// Cuts: where each of many choices (a step's case, over a run of k steps) multiplies the cases, a fact about each
|
|
215
|
+
// choice's value that holds whichever way it goes is split on first: its failing side is refuted by deciding that
|
|
216
|
+
// choice alone, and the rest of the proof assumes it, so the tree is a chain of small refutations instead of every
|
|
217
|
+
// combination. For a term column t = if c then A else B the candidates are t ≤ A, t ≤ B, t ≥ A and t ≥ B, innermost
|
|
218
|
+
// choices first; a cut is only a split, so the kernel checks it like any other.
|
|
219
|
+
function withCuts(goal, path, budget) {
|
|
220
|
+
const rows = leafConstraints(goal, path);
|
|
221
|
+
const columns = [...new Set(rows.flatMap(r => Object.keys(r.lin)).filter(n => n.startsWith('⟦')))]
|
|
222
|
+
.map(n => ({ n, e: columnTerm(n) })).filter(x => x.e.op === 'if' && !decided(x.e, path))
|
|
223
|
+
.sort((a, b) => Number(a.n.slice(1, -1)) - Number(b.n.slice(1, -1))); // a term's parts get their ids before it: innermost first
|
|
224
|
+
const chain = [];
|
|
225
|
+
let at = new Map(path);
|
|
226
|
+
for (const { n, e } of columns) {
|
|
227
|
+
if (e.op !== 'if')
|
|
228
|
+
continue;
|
|
229
|
+
const a = linear(e.then, at), b = linear(e.else, at), focus = topAtoms(e.c, at);
|
|
230
|
+
if (!a || !b || !focus.length)
|
|
231
|
+
continue;
|
|
232
|
+
for (const lemma of [sub(v(n), a), sub(v(n), b), sub(a, v(n)), sub(b, v(n))]) {
|
|
233
|
+
const id = canon(lemma);
|
|
234
|
+
if (at.has(id) || Object.keys(lemma).every(key => key === '1'))
|
|
235
|
+
continue;
|
|
236
|
+
const no = growFocused(goal, new Map(at).set(id, { lin: lemma, holds: false }), focus, { nodes: 0 });
|
|
237
|
+
if ('tree' in no) {
|
|
238
|
+
chain.push({ split: lemma, no: no.tree });
|
|
239
|
+
at = new Map(at).set(id, { lin: lemma, holds: true });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (!chain.length)
|
|
244
|
+
return undefined;
|
|
245
|
+
const rest = growPlain(goal, at, budget);
|
|
246
|
+
if (!('tree' in rest))
|
|
247
|
+
return rest;
|
|
248
|
+
return { tree: chain.reduceRight((acc, c) => ({ split: c.split, yes: acc, no: c.no }), rest.tree), nodes: budget.nodes };
|
|
249
|
+
}
|
|
250
|
+
// A condition's own comparisons, each a split atom, read with term columns for whatever choices are inside it.
|
|
251
|
+
function topAtoms(c, path) {
|
|
252
|
+
if (c.op === 'not')
|
|
253
|
+
return topAtoms(c.x, path);
|
|
254
|
+
if (c.op === 'and' || c.op === 'or')
|
|
255
|
+
return c.xs.flatMap(y => topAtoms(y, path));
|
|
256
|
+
if (c.op === 'flag' || c.op === 'all')
|
|
257
|
+
return [];
|
|
258
|
+
const a = linear(c.a, path), b = linear(c.b, path);
|
|
259
|
+
if (!a || !b)
|
|
260
|
+
return [];
|
|
261
|
+
const atoms = c.op === '<=' ? [sub(a, b)] : c.op === '<' ? [add(sub(a, b), k(1))] : [sub(a, b), sub(b, a)];
|
|
262
|
+
return atoms.filter(l => Object.keys(l).some(key => key !== '1') && !path.has(canon(l)) && !path.has(canon(add(k(1), scale(l, -1)))));
|
|
263
|
+
}
|
|
264
|
+
// A refutation that splits only on these atoms (a cut's failing side), within a small budget.
|
|
265
|
+
function growFocused(goal, path, focus, budget, near = focus) {
|
|
266
|
+
if (++budget.nodes > 64)
|
|
267
|
+
return { stuck: 'unknown', path };
|
|
268
|
+
const verdict = solveNear(leafConstraints(goal, path), [...near, ...[...path.values()].slice(-1).map(a => a.lin)]);
|
|
269
|
+
if (verdict.kind === 'refuted')
|
|
270
|
+
return { tree: { leaf: verdict.steps }, nodes: budget.nodes };
|
|
271
|
+
const split = focus.find(l => !path.has(canon(l)) && !path.has(canon(add(k(1), scale(l, -1)))));
|
|
272
|
+
if (!split)
|
|
273
|
+
return { stuck: verdict.kind === 'sat' ? 'sat' : 'unknown', path };
|
|
274
|
+
const id = canon(split);
|
|
275
|
+
const yes = growFocused(goal, new Map(path).set(id, { lin: split, holds: true }), focus, budget, near);
|
|
178
276
|
if (!('tree' in yes))
|
|
179
277
|
return yes;
|
|
180
|
-
const no =
|
|
278
|
+
const no = growFocused(goal, new Map(path).set(id, { lin: split, holds: false }), focus, budget, near);
|
|
181
279
|
if (!('tree' in no))
|
|
182
280
|
return no;
|
|
183
281
|
return { tree: { split, yes: yes.tree, no: no.tree }, nodes: budget.nodes };
|
|
184
282
|
}
|
|
283
|
+
// A refutation from the rows near some facts (the rows reachable from their variables in two hops, each row joining the
|
|
284
|
+
// variables it reads), its inputs renumbered to the whole leaf's. A cut about one step of a long run needs that step's
|
|
285
|
+
// rows, not the run's; a cut it misses is only a cut not made (the plain search still follows).
|
|
286
|
+
function solveNear(rows, seeds) {
|
|
287
|
+
const reach = new Set(seeds.flatMap(l => Object.keys(l).filter(key => key !== '1')));
|
|
288
|
+
for (let hop = 0; hop < 2; hop++)
|
|
289
|
+
for (const r of rows) {
|
|
290
|
+
const keys = Object.keys(r.lin).filter(key => key !== '1');
|
|
291
|
+
if (keys.some(key => reach.has(key)))
|
|
292
|
+
keys.forEach(key => reach.add(key));
|
|
293
|
+
}
|
|
294
|
+
const picked = rows.flatMap((r, i) => (Object.keys(r.lin).every(key => key === '1' || reach.has(key)) ? [i] : []));
|
|
295
|
+
const near = solve(picked.map(i => rows[i]));
|
|
296
|
+
return near.kind === 'refuted' ? { kind: 'refuted', steps: near.steps.map(st => (st.kind === 'input' ? { ...st, index: picked[st.index] } : st)) } : near;
|
|
297
|
+
}
|
|
185
298
|
// Integer completeness for bounded systems: when elimination is inexact and finds no witness, halve a variable's range
|
|
186
299
|
// (x <= mid or x >= mid + 1). Each half is a split like any other, so the kernel checks it the same way; a variable
|
|
187
300
|
// pinned to one value becomes an equality, which the solver substitutes exactly.
|
|
@@ -363,6 +476,42 @@ function match(pattern, term, x) {
|
|
|
363
476
|
const witnesses = (m) => Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [{ op: 'var', name: `w?${f}` }] : []));
|
|
364
477
|
// The keys each quantified flag is assumed at (instances under ?f).
|
|
365
478
|
const flagInstances = (m, keys) => Object.fromEntries(Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => (c.op === 'all' ? [[`?${f}`, keys]] : [])));
|
|
479
|
+
// The instances some goals need, for every entry point: the every-key laws and the quantified flags at `seed` (the keys
|
|
480
|
+
// the step and the laws read, when known before any goal is built), then twice round: the facts at every key the goals
|
|
481
|
+
// built so far read a function at, and, where the seed doesn't cover them (no seed, positions that shift, a law about
|
|
482
|
+
// several keys), the every-key laws and flags at every key they read a map at (a law about several keys at tuples
|
|
483
|
+
// relating `focus`, the goals' own keys).
|
|
484
|
+
function instancesFor(m, laws, build, opts = {}) {
|
|
485
|
+
let inst = opts.start ?? (opts.seed ? { ...Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, l.keys?.length ? [] : opts.seed])), ...flagInstances(m, opts.seed) } : {});
|
|
486
|
+
const reads = opts.reads ?? (!opts.seed || shifts(m) || laws.some(l => l.keys?.length));
|
|
487
|
+
for (let round = 0; round < 2; round++) {
|
|
488
|
+
if (m.facts?.length)
|
|
489
|
+
inst = withFacts(m, inst, fnKeys(m, build(inst)));
|
|
490
|
+
if (reads) {
|
|
491
|
+
const keys = mapKeys(build(inst));
|
|
492
|
+
inst = union(withReads(laws, inst, keys, opts.focus ?? []), flagInstances(m, keys));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return inst;
|
|
496
|
+
}
|
|
497
|
+
// Instances without the keys that read any of these names (a law about several keys loses each tuple that does).
|
|
498
|
+
const avoiding = (inst, names, laws) => {
|
|
499
|
+
const bad = (e) => names.some(n => JSON.stringify(e).includes(`"name":${JSON.stringify(n)}`));
|
|
500
|
+
return Object.fromEntries(Object.entries(inst).map(([n, keys]) => {
|
|
501
|
+
const arity = (laws.find(l => l.name === n)?.keys?.length ?? 0) + 1;
|
|
502
|
+
const tuples = Array.from({ length: Math.floor(keys.length / arity) }, (_, t) => keys.slice(t * arity, (t + 1) * arity));
|
|
503
|
+
return [n, tuples.filter(tuple => !tuple.some(bad)).flat()];
|
|
504
|
+
}));
|
|
505
|
+
};
|
|
506
|
+
// Two sets of instances together: each name at the keys of both.
|
|
507
|
+
const union = (a, b) => {
|
|
508
|
+
const out = { ...a };
|
|
509
|
+
for (const [n, keys] of Object.entries(b)) {
|
|
510
|
+
const seen = new Set((out[n] ?? []).map(e => JSON.stringify(e)));
|
|
511
|
+
out[n] = [...(out[n] ?? []), ...keys.filter(e => !seen.has(JSON.stringify(e)))];
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
};
|
|
366
515
|
const someKeys = (c, positive = true) => c.op === 'all' ? [...(positive ? [] : [c]), ...someKeys(c.c, positive)] : c.op === 'not' ? someKeys(c.x, !positive) : c.op === 'and' || c.op === 'or' ? c.xs.flatMap(y => someKeys(y, positive)) : [];
|
|
367
516
|
function atWitness(c, picks, positive = true) {
|
|
368
517
|
switch (c.op) {
|
|
@@ -427,29 +576,20 @@ export function proveLaws(m, laws) {
|
|
|
427
576
|
return { ...proveLawsAsGiven(m, out.items), laws: out.items };
|
|
428
577
|
}
|
|
429
578
|
function proveLawsAsGiven(m, laws) {
|
|
430
|
-
|
|
431
|
-
const instances = {};
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const goals = obligations(m, laws, instances);
|
|
439
|
-
laws.forEach((law, i) => { instances[law.name] = withFacts(m, instances[law.name], fnKeys(m, goals.slice(2 * i, 2 * i + 2))); });
|
|
440
|
-
}
|
|
441
|
-
// Where positions shift (a write at every key reading its key at k ∸ 1 or k + 1) or a law is about two keys, the
|
|
442
|
-
// every-key laws also at each key the goals read a map at, once round.
|
|
443
|
-
if (shifts(m) || laws.some(l => l.keys?.length)) {
|
|
444
|
-
const goals = obligations(m, laws, instances);
|
|
445
|
-
laws.forEach((law, i) => { instances[law.name] = withReads(laws, instances[law.name], mapKeys(goals.slice(2 * i, 2 * i + 2)), [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])]); });
|
|
446
|
-
}
|
|
579
|
+
// Each law's two goals (start and step) get their own instances, seeded with its own keys.
|
|
580
|
+
const keys = [...readKeys(m, laws), ...witnesses(m)], instances = {};
|
|
581
|
+
laws.forEach((law, i) => {
|
|
582
|
+
instances[law.name] = instancesFor(m, laws, inst => obligations(m, laws, { ...instances, [law.name]: inst }).slice(2 * i, 2 * i + 2), {
|
|
583
|
+
seed: [...keys, ...(law.forall ? [{ op: 'var', name: `${law.forall}#` }] : []), ...(law.keys ?? []).map((name) => ({ op: 'var', name }))],
|
|
584
|
+
focus: [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])],
|
|
585
|
+
});
|
|
586
|
+
});
|
|
447
587
|
const trees = {};
|
|
448
588
|
let nodes = 0, leaves = 0;
|
|
449
589
|
for (const goal of obligations(m, laws, instances)) {
|
|
450
590
|
const out = grow(goal);
|
|
451
591
|
if (!('tree' in out))
|
|
452
|
-
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
|
|
592
|
+
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
|
|
453
593
|
trees[goal.name] = out.tree;
|
|
454
594
|
nodes += out.nodes;
|
|
455
595
|
leaves += countLeaves(out.tree);
|
|
@@ -476,23 +616,26 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
|
|
|
476
616
|
if (!base.proof)
|
|
477
617
|
return { proven: false, nodes: base.nodes, leaves: base.leaves, checked: false };
|
|
478
618
|
const plain = m.history ? historyMachine(m).machine : m;
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
619
|
+
// Each step law its own instances, from its own goal: the keys the step and the laws read, and its own (a k-step
|
|
620
|
+
// law's later frames read keys no other step law has); where positions shift or a law is about several keys, the
|
|
621
|
+
// keys its goal reads a map at too. A frame's hypotheses read no name the run introduces later (kernel.ts's
|
|
622
|
+
// stepZone), so a k-step law drops the keys that read one.
|
|
623
|
+
const shared = [...readKeys(plain, laws), ...witnesses(plain)], named = [...laws, ...(plain.facts ?? [])], reads = shifts(plain) || laws.some(l => l.keys?.length);
|
|
624
|
+
const own = (sl) => [...[sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC),
|
|
625
|
+
...[sl.forall, ...(sl.keys ?? [])].flatMap(key => (key ? [{ op: 'var', name: key }] : []))];
|
|
485
626
|
const perStep = {};
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
627
|
+
for (const sl of stepLaws) {
|
|
628
|
+
const zone = stepZone(plain, sl.steps ?? 1), keep = (inst) => avoiding(inst, zone, named);
|
|
629
|
+
perStep[sl.name] = keep(instancesFor(plain, laws, inst => stepLawObligations(m, laws, keep(inst), sl), { seed: [...shared, ...own(sl)], focus: [sl.forall, ...(sl.keys ?? [])].flatMap(k => (k ? [k] : [])), reads }));
|
|
630
|
+
}
|
|
631
|
+
const instances = perStep[stepLaws[0]?.name ?? ''] ?? {};
|
|
489
632
|
const trees = {};
|
|
490
633
|
let nodes = base.nodes, leaves = base.leaves;
|
|
491
634
|
for (const sl of stepLaws)
|
|
492
635
|
for (const goal of stepLawObligations(m, laws, perStep[sl.name] ?? instances, sl)) {
|
|
493
636
|
const out = grow(goal);
|
|
494
637
|
if (!('tree' in out))
|
|
495
|
-
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
|
|
638
|
+
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
|
|
496
639
|
trees[goal.name] = out.tree;
|
|
497
640
|
nodes += out.nodes;
|
|
498
641
|
leaves += countLeaves(out.tree);
|
|
@@ -507,16 +650,13 @@ export function proveProgress(m, laws, p) {
|
|
|
507
650
|
if (!base.proven || !base.checked)
|
|
508
651
|
return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
|
|
509
652
|
const ls = base.laws ?? laws;
|
|
510
|
-
|
|
511
|
-
instances = { ...instances, ...withReads(ls, instances, mapKeys(progressObligations(m, ls, instances, p)), []) };
|
|
512
|
-
if (m.facts?.length)
|
|
513
|
-
instances = withFacts(m, instances, fnKeys(m, progressObligations(m, ls, instances, p)));
|
|
653
|
+
const instances = instancesFor(m, ls, inst => progressObligations(m, ls, inst, p));
|
|
514
654
|
const trees = {};
|
|
515
655
|
let nodes = base.nodes, leaves = base.leaves;
|
|
516
656
|
for (const goal of progressObligations(m, ls, instances, p)) {
|
|
517
657
|
const out = grow(goal);
|
|
518
658
|
if (!('tree' in out))
|
|
519
|
-
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
|
|
659
|
+
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
|
|
520
660
|
trees[goal.name] = out.tree;
|
|
521
661
|
nodes += out.nodes;
|
|
522
662
|
leaves += countLeaves(out.tree);
|
|
@@ -562,7 +702,7 @@ function readKeysC(c) {
|
|
|
562
702
|
export const countLeaves = (t) => ('leaf' in t ? 1 : countLeaves(t.yes) + countLeaves(t.no));
|
|
563
703
|
// A real counterexample for a goal the prover couldn't close (with products, its witness may be abstract): the free
|
|
564
704
|
// variables over 0..bound, every hypothesis evaluated with true arithmetic. Goals that read maps are left to the
|
|
565
|
-
// machine searches (search/), which run whole histories.
|
|
705
|
+
// machine searches (research/search/), which run whole histories.
|
|
566
706
|
export function concretize(goal, bound = 12) {
|
|
567
707
|
const names = new Set();
|
|
568
708
|
let reads = false;
|
|
@@ -604,16 +744,15 @@ export function concretize(goal, bound = 12) {
|
|
|
604
744
|
}
|
|
605
745
|
return undefined;
|
|
606
746
|
}
|
|
747
|
+
// The facts a goal needs, as hypotheses: each fact about every key at the keys the goal reads a function at (and at
|
|
748
|
+
// the keys those facts read, a second round), each plain fact as it is.
|
|
749
|
+
export function factsFor(m, goal) {
|
|
750
|
+
return assumed(m.facts ?? [], instancesFor(m, [], inst => [{ ...goal, hyps: [...assumed(m.facts ?? [], inst), ...goal.hyps] }]));
|
|
751
|
+
}
|
|
607
752
|
// A goal built from its instances (an exporter's goal, shaped as its Lean twin builds it): the every-key laws, the
|
|
608
753
|
// quantified flags and the facts assumed at every key it reads a map or function at, then a tree, checked.
|
|
609
754
|
export function proveBuilt(m, laws, build) {
|
|
610
|
-
|
|
611
|
-
for (let round = 0; round < 2; round++) {
|
|
612
|
-
const g = build(instances), keys = mapKeys([g]);
|
|
613
|
-
instances = { ...withReads(laws, instances, keys, []), ...flagInstances(m, keys) };
|
|
614
|
-
if (m.facts?.length)
|
|
615
|
-
instances = withFacts(m, instances, fnKeys(m, [build(instances)]));
|
|
616
|
-
}
|
|
755
|
+
const instances = instancesFor(m, laws, inst => [build(inst)]);
|
|
617
756
|
const goal = build(instances), out = grow(goal);
|
|
618
757
|
if (!('tree' in out) || !check(goal, out.tree))
|
|
619
758
|
return { proven: false };
|
|
@@ -57,16 +57,6 @@ export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts
|
|
|
57
57
|
progress?: ProgressCase[];
|
|
58
58
|
free?: string[];
|
|
59
59
|
stepLaws?: StepLaw[];
|
|
60
|
-
liveness?: Liveness[];
|
|
61
|
-
ghosts?: string[];
|
|
62
|
-
after?: (ix: (name: string) => number) => string;
|
|
63
|
-
refined?: {
|
|
64
|
-
impl: Machine;
|
|
65
|
-
spec: Machine;
|
|
66
|
-
wit: Record<string, E>;
|
|
67
|
-
stutter: C;
|
|
68
|
-
specPre: number;
|
|
69
|
-
};
|
|
70
60
|
}): string;
|
|
71
61
|
export declare const hasSets: (m: Machine) => boolean;
|
|
72
62
|
export declare function smachineToLean(id: string, m: Machine, laws: Law[], opts?: {
|
|
@@ -78,6 +68,8 @@ export declare function smachineToLean(id: string, m: Machine, laws: Law[], opts
|
|
|
78
68
|
};
|
|
79
69
|
simp?: string[];
|
|
80
70
|
free?: string[];
|
|
71
|
+
ghosts?: string[];
|
|
72
|
+
after?: (ix: (name: string) => number) => string;
|
|
81
73
|
refined?: {
|
|
82
74
|
impl: Machine;
|
|
83
75
|
spec: Machine;
|