gdp-wired 0.15.0 → 0.17.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 +12 -0
- package/dist/core/chart.js +4 -4
- package/dist/core/cyclic.d.ts +51 -0
- package/dist/core/cyclic.js +295 -0
- package/dist/core/discover.d.ts +1 -1
- package/dist/core/discover.js +10 -5
- package/dist/core/dsl.d.ts +49 -4
- package/dist/core/dsl.js +143 -17
- package/dist/core/infer.js +123 -11
- package/dist/kernel/cyclic.d.ts +45 -0
- package/dist/kernel/cyclic.js +54 -0
- package/dist/kernel/prove.d.ts +10 -0
- package/dist/kernel/prove.js +348 -2
- package/dist/lean-kernel/emit.d.ts +13 -0
- package/dist/lean-kernel/emit.js +50 -2
- package/dist/lean-kernel/library.js +1 -0
- package/package.json +6 -2
package/dist/core/dsl.js
CHANGED
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf, pairedArgs } from "./machine.js";
|
|
19
19
|
import { allowed, keysOf, requestEnv, watch } from "./watch.js";
|
|
20
20
|
import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
|
|
21
|
-
import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws } from "../kernel/prove.js";
|
|
21
|
+
import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws, withDeadline } from "../kernel/prove.js";
|
|
22
|
+
import { crossCyclic, cyclicOn, foldNodes, proveCyclic, quietly, recurNodes } from "./cyclic.js";
|
|
23
|
+
import { discover } from "./discover.js";
|
|
24
|
+
import { inferLaws } from "./infer.js";
|
|
22
25
|
// --- terms and conditions -------------------------------------------------------------------------------------------
|
|
23
26
|
const naturalConst = (v) => {
|
|
24
27
|
if (!Number.isSafeInteger(v) || v < 0)
|
|
@@ -860,6 +863,43 @@ export class ListRef {
|
|
|
860
863
|
within() { return forall(k => this.len.atMost(k).implies(this.at(k).eq(0))); }
|
|
861
864
|
increasing() { return forall((i, j) => i.lt(j).and(j.lt(this.len)).implies(this.at(i).lt(this.at(j)))); }
|
|
862
865
|
}
|
|
866
|
+
// A sequence of naturals as positions and a length (a path's segments, as interned names): `at(i)` its value at i,
|
|
867
|
+
// `len` its length. Comparing two is a condition about every position below a length (the array property fragment,
|
|
868
|
+
// decidable), so a law about prefixes needs no induction. One a request carries (`carriesSeq`), or a row of a table of
|
|
869
|
+
// them (`carriesSeqs`), its positions read from its row's offset in the table's one map.
|
|
870
|
+
export class SeqRef {
|
|
871
|
+
map;
|
|
872
|
+
len;
|
|
873
|
+
base;
|
|
874
|
+
constructor(map, len, base) {
|
|
875
|
+
this.map = map;
|
|
876
|
+
this.len = len;
|
|
877
|
+
this.base = base;
|
|
878
|
+
}
|
|
879
|
+
at(i) { return this.map.at(!this.base ? i : typeof i === 'number' ? this.base.plus(i) : i.plus(this.base)); }
|
|
880
|
+
// The same values at every position below `upTo` (both have them).
|
|
881
|
+
agree(o, upTo) { return everyKey(k => k.lt(upTo).implies(this.at(k).eq(o.at(k)))); }
|
|
882
|
+
// This one is where o starts; o is where this one starts; the same; o followed by `by` more; x at some position.
|
|
883
|
+
prefixOf(o) { return this.len.atMost(o.len).and(this.agree(o, this.len)); }
|
|
884
|
+
startsWith(o) { return o.prefixOf(this); }
|
|
885
|
+
equals(o) { return this.len.eq(o.len).and(this.agree(o, this.len)); }
|
|
886
|
+
extends(o, by) { return o.prefixOf(this).and(this.len.eq(o.len.plus(by))); }
|
|
887
|
+
has(x) { return everyKey(k => k.lt(this.len).implies(this.at(k).ne(x))).not(); }
|
|
888
|
+
}
|
|
889
|
+
// A table of sequences a request carries (each grant's path, say): `row(r)` is row r's sequence, `rows` how many. Rows
|
|
890
|
+
// are read by their number as a map's key, so a condition about every row reads a row only there (`everyBelow(T.rows,
|
|
891
|
+
// r => ...)`).
|
|
892
|
+
export class SeqTable {
|
|
893
|
+
name;
|
|
894
|
+
rows;
|
|
895
|
+
constructor(name, rows) {
|
|
896
|
+
this.name = name;
|
|
897
|
+
this.rows = rows;
|
|
898
|
+
}
|
|
899
|
+
row(r) { return new SeqRef(new MapRef(this.name, 0), new MapRef(`${this.name}_len`, 0).at(r), new MapRef(`${this.name}_base`, 0).at(r)); }
|
|
900
|
+
}
|
|
901
|
+
// f at every key below n: what bookkeeping a request carries meets, row by row (where each grant went, say).
|
|
902
|
+
export const everyBelow = (n, f) => everyKey(k => k.lt(n).implies(f(k)));
|
|
863
903
|
const when_ = (c, then, otherwise) => new Term({ op: 'if', c: c.c, then: termOf(then), else: termOf(otherwise) }, c.ok === undefined ? undefined : valOf(c.ok ? then : otherwise));
|
|
864
904
|
const isPartsWrite = (u) => typeof u === 'object' && u !== null && 'parts' in u;
|
|
865
905
|
// The parts of a value written at a cell, by the names of the maps they go to.
|
|
@@ -995,7 +1035,7 @@ export class CarriedList {
|
|
|
995
1035
|
.state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, typeof d.from === 'number' ? d.from : (p) => d.from(p)])) })
|
|
996
1036
|
.step((env) => ({ i: env.i.plus(1), ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, d.step(env[`acc$${f}`], item(env.i), env)])) }));
|
|
997
1037
|
const accs = (env) => Object.fromEntries(folds.map(([f]) => [f, env[`acc$${f}`]]));
|
|
998
|
-
return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i, env))));
|
|
1038
|
+
return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i, env))), { search: false });
|
|
999
1039
|
}
|
|
1000
1040
|
// Every carried map it is kept in: the items' parts, then each fold's.
|
|
1001
1041
|
get maps() { return [...this.kind.vars(this.name), ...Object.keys(this.folds).map(f => `${this.name}$${f}`)]; }
|
|
@@ -1360,6 +1400,10 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1360
1400
|
const stated = Object.entries(lemmas).map(([n, f]) => { const holds = f(named('x'), named('fx'), envAt(own)).c; only(`lemma "${n}"`, holds, ['x', 'fx', ...args], uses.map(([u]) => u)); return { name: n, holds }; });
|
|
1361
1401
|
const pairs = Object.entries(between).map(([n, f]) => { const holds = f(named('x'), named('fx'), named('y'), named('fy'), envAt(own)).c; only(`law between "${n}"`, holds, ['x', 'fx', 'y', 'fy', ...args], uses.map(([u]) => u)); return { name: n, holds }; });
|
|
1362
1402
|
const argKinds = Object.fromEntries(args.map(a => [a, nat]));
|
|
1403
|
+
// The recurrence as a cyclic proof's instance (core/cyclic.ts, GDP_CYCLIC=1): the facts of the functions it reads, from
|
|
1404
|
+
// a rule that reads them.
|
|
1405
|
+
const recurCase = (between) => ({ name, args, zero, next, lemmas: stated, between,
|
|
1406
|
+
facts: uses.length ? rule(`${name}'s functions`).fns(def.fns ?? {}).inputs({}).state({ z: 0 }).step((env) => ({ z: env.z })).machine.facts ?? [] : [] });
|
|
1363
1407
|
const failed = (what, v) => { if (!(v.proven && v.checked))
|
|
1364
1408
|
throw new Unproven(`${name}'s ${what}`, v); };
|
|
1365
1409
|
// The lemmas: x counting up from 0 and fx its value, each lemma a law of that machine.
|
|
@@ -1374,7 +1418,11 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1374
1418
|
const r = r0.state({ x: 0, fx: ((p) => (zeroReads ? p.fx0 : zeroAt(args.map(a => p[a])))) })
|
|
1375
1419
|
.step((env) => ({ x: env.x.plus(1), fx: def.next(env.x, env.fx, envAt(args.map(a => env[a]))) }));
|
|
1376
1420
|
const laws = Object.entries(lemmas).map(([n, f]) => r.law(n, (env) => f(env.x, env.fx, envAt(args.map(a => env[a])))));
|
|
1377
|
-
|
|
1421
|
+
// (Its machine's proof timed alone: the recurrence is compared below as its own instance.)
|
|
1422
|
+
const t0 = performance.now();
|
|
1423
|
+
verdict = quietly(() => proveRule(r, laws, { search: false }));
|
|
1424
|
+
if (cyclicOn())
|
|
1425
|
+
crossCyclic('recurrence', name, verdict.proven && verdict.checked, () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0);
|
|
1378
1426
|
failed('lemmas', verdict);
|
|
1379
1427
|
proofs.lemmas = { machine: r.machine, laws };
|
|
1380
1428
|
}
|
|
@@ -1411,7 +1459,9 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1411
1459
|
.step((env) => ({ y: env.y.plus(1), fy: def.next(env.y, env.fy, envAt(at(env))) }));
|
|
1412
1460
|
const laws = [...Object.entries(lemmas).map(([n, l]) => r.law(`${n} at y`, (env) => l(env.y, env.fy, envAt(at(env))))),
|
|
1413
1461
|
...Object.entries(between).map(([n, l]) => r.law(n, (env) => l(env.x, env.fx, env.y, env.fy, envAt(at(env)))))];
|
|
1414
|
-
const v = proveRule(r, laws);
|
|
1462
|
+
const t0 = performance.now(), v = quietly(() => proveRule(r, laws, { search: false })), usualMs = performance.now() - t0;
|
|
1463
|
+
if (cyclicOn())
|
|
1464
|
+
crossCyclic('recurrence, between', name, v.proven && v.checked, () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs);
|
|
1415
1465
|
failed('laws between two points', v);
|
|
1416
1466
|
proofs.between = { machine: r.machine, laws };
|
|
1417
1467
|
verdict = { proven: true, checked: verdict.checked && v.checked };
|
|
@@ -1631,6 +1681,16 @@ export class Rule {
|
|
|
1631
1681
|
facts(f) { return this.with({ facts: [...(this.spec.facts ?? []), f] }); }
|
|
1632
1682
|
// Maps each request carries whole (a set of names, say: cells 0 or 1), every cell past them 0; read like any map.
|
|
1633
1683
|
carries(...names) { return this.with({ carried: [...(this.spec.carried ?? []), ...names] }); }
|
|
1684
|
+
// Sequences each request carries (SeqRef): `.carriesSeq('Path')`, then `Path.at(i)`, `Path.len`, `A.prefixOf(B)`; the
|
|
1685
|
+
// request gives each as an array of naturals.
|
|
1686
|
+
carriesSeq(...names) {
|
|
1687
|
+
return this.with({ carried: [...(this.spec.carried ?? []), ...names], inputs: [...this.spec.inputs, ...names.map(n => [`${n}_len`, nat])], seqs: [...(this.spec.seqs ?? []), ...names] });
|
|
1688
|
+
}
|
|
1689
|
+
// Tables of sequences each request carries (SeqTable): `.carriesSeqs('Paths')`, then `Paths.row(r)`, `Paths.rows`; the
|
|
1690
|
+
// request gives each as an array of arrays of naturals.
|
|
1691
|
+
carriesSeqs(...names) {
|
|
1692
|
+
return this.with({ carried: [...(this.spec.carried ?? []), ...names.flatMap(n => [n, `${n}_base`, `${n}_len`])], inputs: [...this.spec.inputs, ...names.map(n => [`${n}_rows`, nat])], seqTables: [...(this.spec.seqTables ?? []), ...names] });
|
|
1693
|
+
}
|
|
1634
1694
|
// A list of values of a kind each request carries, with folds over it and lemmas about the folds (CarriedList):
|
|
1635
1695
|
// .carriesList('Lines', record({amount: nat}), {total: {from: 0, step: (acc, line) => acc.plus(line.amount)}})
|
|
1636
1696
|
// Each fold's start and step, and each lemma, may read the params declared before it.
|
|
@@ -1846,6 +1906,15 @@ export class Rule {
|
|
|
1846
1906
|
out[n] = k.value(n);
|
|
1847
1907
|
for (const n of this.spec.carried ?? [])
|
|
1848
1908
|
out[n] = new MapRef(n, 0);
|
|
1909
|
+
for (const n of this.spec.seqs ?? []) {
|
|
1910
|
+
delete out[`${n}_len`];
|
|
1911
|
+
out[n] = new SeqRef(new MapRef(n, 0), v(`${n}_len`));
|
|
1912
|
+
}
|
|
1913
|
+
for (const n of this.spec.seqTables ?? []) {
|
|
1914
|
+
for (const part of [`${n}_base`, `${n}_len`, `${n}_rows`])
|
|
1915
|
+
delete out[part];
|
|
1916
|
+
out[n] = new SeqTable(n, v(`${n}_rows`));
|
|
1917
|
+
}
|
|
1849
1918
|
const ps = Object.fromEntries(this.spec.params.map(([n]) => [n, out[n]]));
|
|
1850
1919
|
for (const [n, k, fs, ls] of this.spec.lists2 ?? []) {
|
|
1851
1920
|
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, ps, this.spec.params);
|
|
@@ -2187,16 +2256,35 @@ export class Rule {
|
|
|
2187
2256
|
// --- running it -----------------------------------------------------------------------------------------------------
|
|
2188
2257
|
// Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
|
|
2189
2258
|
encodeParams(values) { return encode(this.spec.params, values); }
|
|
2190
|
-
// A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural
|
|
2259
|
+
// A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural; a sequence an array of
|
|
2260
|
+
// naturals, a table of them an array of such arrays.
|
|
2191
2261
|
// A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
|
|
2192
2262
|
// folds that read the params are computed with.
|
|
2193
2263
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2194
2264
|
encodeInput(values, params) {
|
|
2195
2265
|
const at = params && this.paramValues(this.encodeParams(params));
|
|
2196
2266
|
const lists = Object.fromEntries((this.spec.lists2 ?? []).map(([n, k, fs]) => [n, new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, at).encode(values[n])]));
|
|
2197
|
-
|
|
2267
|
+
// A sequence: its values at their positions (no 0 cell), its length; a table: the rows one after another in one map,
|
|
2268
|
+
// each row's offset and length.
|
|
2269
|
+
const seqMaps = {}, seqLens = {};
|
|
2270
|
+
const cells = (xs, from = 0) => xs.flatMap((x, i) => (x ? [[from + i, x]] : []));
|
|
2271
|
+
for (const n of this.spec.seqs ?? []) {
|
|
2272
|
+
const xs = values[n] ?? [];
|
|
2273
|
+
seqMaps[n] = new Map(cells(xs));
|
|
2274
|
+
seqLens[`${n}_len`] = xs.length;
|
|
2275
|
+
}
|
|
2276
|
+
for (const n of this.spec.seqTables ?? []) {
|
|
2277
|
+
const rows = values[n] ?? [], base = [];
|
|
2278
|
+
rows.reduce((at, r) => (base.push(at), at + r.length), 0);
|
|
2279
|
+
seqMaps[n] = new Map(rows.flatMap((r, i) => cells(r, base[i])));
|
|
2280
|
+
seqMaps[`${n}_base`] = new Map(base.flatMap((b, i) => (b ? [[i, b]] : [])));
|
|
2281
|
+
seqMaps[`${n}_len`] = new Map(rows.flatMap((r, i) => (r.length ? [[i, r.length]] : [])));
|
|
2282
|
+
seqLens[`${n}_rows`] = rows.length;
|
|
2283
|
+
}
|
|
2284
|
+
const out = encode(this.spec.inputs, { ...values, ...seqLens, ...Object.fromEntries(Object.entries(lists).map(([n, l]) => [`${n}_len`, l.len])) });
|
|
2198
2285
|
for (const l of Object.values(lists))
|
|
2199
2286
|
Object.assign(out, l.maps);
|
|
2287
|
+
Object.assign(out, seqMaps);
|
|
2200
2288
|
for (const n of this.spec.carried ?? []) {
|
|
2201
2289
|
if (n in out)
|
|
2202
2290
|
continue;
|
|
@@ -2432,14 +2520,16 @@ const withKinds = (r, laws) => [...r.kindLaws().filter(k => !laws.some(l => l.na
|
|
|
2432
2520
|
// interpreter took. A rule that reads functions is skipped: the kernel knows them only by their facts (a recurrence's
|
|
2433
2521
|
// induction machine reads the function it proves things about), so a run computing them proves nothing about the goal.
|
|
2434
2522
|
const refuteBudget = { trials: 60, depth: 6, bound: 3, ms: 150 };
|
|
2435
|
-
|
|
2523
|
+
// The search's own runs, before it looks for what the laws need: deeper and wider, from another seed.
|
|
2524
|
+
const deeperBudget = { trials: 600, depth: 24, bound: 8, ms: 1500, seed: 29 };
|
|
2525
|
+
function refuteByRuns(r, laws, stepLaws = [], budget = refuteBudget) {
|
|
2436
2526
|
const m = r.machine;
|
|
2437
2527
|
if (Object.keys(m.fns ?? {}).length || m.facts?.length)
|
|
2438
2528
|
return undefined;
|
|
2439
|
-
const run = interpret(m), rand = seeded(13), until = performance.now() +
|
|
2529
|
+
const run = interpret(m), rand = seeded(budget.seed ?? 13), until = performance.now() + budget.ms, bound = budget.bound;
|
|
2440
2530
|
const shown = (input) => Object.fromEntries(Object.entries(input).map(([n, x]) => [n, typeof x === 'number' ? x : Object.fromEntries(x)]));
|
|
2441
2531
|
try {
|
|
2442
|
-
for (let trial = 0; trial <
|
|
2532
|
+
for (let trial = 0; trial < budget.trials && performance.now() < until; trial++) {
|
|
2443
2533
|
const params = r.randomParams(rand, bound);
|
|
2444
2534
|
if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
|
|
2445
2535
|
continue;
|
|
@@ -2451,7 +2541,7 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2451
2541
|
for (const { w, on } of watching)
|
|
2452
2542
|
if (!('then' in w) && !on.start(st))
|
|
2453
2543
|
return broken(w, '@start', st);
|
|
2454
|
-
for (let d = 0, tries = 0; d <
|
|
2544
|
+
for (let d = 0, tries = 0; d < budget.depth && tries < 40; tries++) {
|
|
2455
2545
|
const input = r.randomInput(rand, bound, params);
|
|
2456
2546
|
if (!allowed(m, run, params, st, input))
|
|
2457
2547
|
continue;
|
|
@@ -2471,7 +2561,7 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2471
2561
|
// variable of a kind is drawn as its kind draws (33 yes/nos drawn from 0..7 almost never make a lawful state).
|
|
2472
2562
|
const steps1 = stepLaws.filter(sl => (sl.steps ?? 1) === 1), given = withKinds(r, laws);
|
|
2473
2563
|
const wide = Object.keys(m.state).length + m.inputs.length >= 32, lawsHere = wide ? given : [], kinded = wide ? r.kindsOfState() : [];
|
|
2474
|
-
for (let trial = 0; !m.history && (steps1.length || lawsHere.length) && trial <
|
|
2564
|
+
for (let trial = 0; !m.history && (steps1.length || lawsHere.length) && trial < budget.trials * 4 && performance.now() < until; trial++) {
|
|
2475
2565
|
const params = r.randomParams(rand, bound);
|
|
2476
2566
|
if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
|
|
2477
2567
|
continue;
|
|
@@ -2499,25 +2589,56 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2499
2589
|
return undefined;
|
|
2500
2590
|
}
|
|
2501
2591
|
const refutedVerdict = (out) => ({ proven: false, checked: false, goal: out.goal, stuck: 'sat', witness: out.witness, ...(out.run ? { run: out.run } : {}) });
|
|
2502
|
-
export function proveRule(r, laws) {
|
|
2592
|
+
export function proveRule(r, laws, opts = {}) {
|
|
2503
2593
|
const refuted = refuteByRuns(r, laws);
|
|
2504
2594
|
if (refuted)
|
|
2505
2595
|
return refutedVerdict(refuted);
|
|
2506
2596
|
const out = proveLaws(r.machine, withKinds(r, laws));
|
|
2507
|
-
|
|
2597
|
+
const v = { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
|
|
2598
|
+
return searched(r, laws, [], v, opts);
|
|
2508
2599
|
}
|
|
2509
|
-
export function proveSteps(r, laws, stepLaws) {
|
|
2600
|
+
export function proveSteps(r, laws, stepLaws, opts = {}) {
|
|
2510
2601
|
const refuted = refuteByRuns(r, laws, stepLaws);
|
|
2511
2602
|
if (refuted)
|
|
2512
2603
|
return refutedVerdict(refuted);
|
|
2513
2604
|
const out = proveStepLaws(r.machine, withKinds(r, laws), stepLaws);
|
|
2514
|
-
|
|
2605
|
+
const v = { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
|
|
2606
|
+
return searched(r, laws, stepLaws, v, opts);
|
|
2607
|
+
}
|
|
2608
|
+
// The search, where the prover alone didn't prove the laws and nothing refuted them: the lemmas first where the rule has
|
|
2609
|
+
// recurrences or carried lists (the rule made again with them), then invariants over what it then is. The verdict is the
|
|
2610
|
+
// prover's on the laws with what was found; when nothing is found, the prover's own (with why).
|
|
2611
|
+
function searched(r, laws, stepLaws, v, opts) {
|
|
2612
|
+
if ((v.proven && v.checked) || opts.search === false || v.run || v.stuck === 'sat' && !/@step|@\d+ steps/.test(v.goal ?? ''))
|
|
2613
|
+
return v;
|
|
2614
|
+
const deeper = refuteByRuns(r, laws, stepLaws, deeperBudget);
|
|
2615
|
+
if (deeper?.run)
|
|
2616
|
+
return refutedVerdict(deeper);
|
|
2617
|
+
const until = performance.now() + (typeof opts.search === 'object' && opts.search.ms !== undefined ? opts.search.ms : 10_000);
|
|
2618
|
+
let rule = r, lemmas;
|
|
2619
|
+
const { fns, lists } = r.parts;
|
|
2620
|
+
if (lists.length || fns.some(([, d]) => recurOf(d))) {
|
|
2621
|
+
const d = withDeadline(until, () => discover(r, laws, stepLaws, v));
|
|
2622
|
+
if (d.verdict.proven && d.verdict.checked)
|
|
2623
|
+
return { ...d.verdict, found: { lemmas: d.found } };
|
|
2624
|
+
if (Object.keys(d.found).length)
|
|
2625
|
+
(rule = d.rule, lemmas = d.found);
|
|
2626
|
+
}
|
|
2627
|
+
const ms = until - performance.now();
|
|
2628
|
+
if (ms < 500)
|
|
2629
|
+
return v;
|
|
2630
|
+
const inf = inferLaws(rule, laws, stepLaws, { ms, ...(rule === r ? { first: v } : {}) });
|
|
2631
|
+
if (!(inf.proven && inf.checked))
|
|
2632
|
+
return { ...v, why: [v.why, `the search found nothing that proves it in ${Math.round(inf.stats.ms)} ms`].filter(Boolean).join('; ') };
|
|
2633
|
+
const { found, stats: _stats, ...verdict } = inf;
|
|
2634
|
+
void _stats;
|
|
2635
|
+
return { ...verdict, found: { laws: found, ...(lemmas ? { lemmas } : {}) } };
|
|
2515
2636
|
}
|
|
2516
2637
|
export function proveLeadsTo(r, laws, l) {
|
|
2517
2638
|
const out = proveLiveness(r.machine, withKinds(r, laws), l);
|
|
2518
2639
|
return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
|
|
2519
2640
|
}
|
|
2520
|
-
export const proveDecision = (d) => (d.steps ? proveSteps(d.steps.rule, [], [d.steps.law]) : goalsVerdict([d.goal]));
|
|
2641
|
+
export const proveDecision = (d) => (d.steps ? proveSteps(d.steps.rule, [], [d.steps.law], { search: false }) : goalsVerdict([d.goal]));
|
|
2521
2642
|
const seeded = (seed) => { let x = seed; return k => { x = (x * 1103515245 + 12345) % 2 ** 31; return Math.floor(x / 2 ** 31 * k); }; };
|
|
2522
2643
|
// A decision's callbacks against its condition, on random params and requests: `given` and `then` each run with every
|
|
2523
2644
|
// value known, against what the interpreter evaluates of the decision built from them.
|
|
@@ -2661,7 +2782,12 @@ export class FoldRule {
|
|
|
2661
2782
|
}
|
|
2662
2783
|
export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
|
|
2663
2784
|
// A fold's laws proven over every tree (a rose fold's own laws, its children's lemmas, with them).
|
|
2664
|
-
export const proveFold = (f, laws) =>
|
|
2785
|
+
export const proveFold = (f, laws) => {
|
|
2786
|
+
const all = [...(f.lemmas ?? []), ...laws], t0 = performance.now(), v = goalsVerdict(foldObligations(f.fold, all)), usualMs = performance.now() - t0;
|
|
2787
|
+
if (cyclicOn())
|
|
2788
|
+
crossCyclic('fold', f.fold.name, v.proven && v.checked, () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs);
|
|
2789
|
+
return v;
|
|
2790
|
+
};
|
|
2665
2791
|
const asTerm = (t) => (t instanceof Term ? t : lit(t));
|
|
2666
2792
|
// A fold over trees whose nodes have any number of children, as the kernel's binary fold (so the kernel and Lean's
|
|
2667
2793
|
// fold_sound check it as they check any fold): a node's children become a chain of glue nodes, each folding one child
|
package/dist/core/infer.js
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
// its witness is a counterexample to induction, so the candidate that rules it out (and the most of those seen) is
|
|
14
14
|
// added; a chosen candidate that nothing supports, or that makes a proof slow, is dropped; the set proven as a whole
|
|
15
15
|
// is the answer, after each chosen candidate the proof doesn't need is taken out again
|
|
16
|
+
import { quietly } from "./cyclic.js";
|
|
16
17
|
import { any, atMost, evalC, get, interpret, n, not, plus, same, times, x } from "./machine.js";
|
|
17
18
|
import { proveRule, proveSteps } from "./dsl.js";
|
|
18
19
|
import { allowed, watch } from "./watch.js";
|
|
19
|
-
import { proveLaw, proveLaws, proveStepLaws } from "../kernel/prove.js";
|
|
20
|
+
import { proveLaw, proveLaws, proveStepLaws, withDeadline } from "../kernel/prove.js";
|
|
20
21
|
const KEY = 'key$found', AT = '@', K = '#';
|
|
21
22
|
const seeded = (seed) => { let s = seed; return k => { s = (s * 1103515245 + 12345) % 2 ** 31; return Math.floor(s / 2 ** 31 * k); }; };
|
|
22
23
|
const isRule = (t) => typeof t.kindsOfState === 'function';
|
|
@@ -106,13 +107,16 @@ const keyValue = (cell, w) => {
|
|
|
106
107
|
return value;
|
|
107
108
|
};
|
|
108
109
|
export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
110
|
+
return quietly(() => withDeadline(performance.now() + (opts.ms ?? 20000), () => inferLawsSearch(target, laws, stepLaws, opts)));
|
|
111
|
+
}
|
|
112
|
+
function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
|
|
109
113
|
const t0 = performance.now(), until = t0 + (opts.ms ?? 20000), rule = isRule(target) ? target : undefined;
|
|
110
114
|
const m = rule ? rule.machine : target;
|
|
111
115
|
let proofs = 0;
|
|
112
116
|
const prove = (ls) => {
|
|
113
117
|
proofs++;
|
|
114
118
|
if (rule)
|
|
115
|
-
return stepLaws.length ? proveSteps(rule, ls, stepLaws) : proveRule(rule, ls);
|
|
119
|
+
return stepLaws.length ? proveSteps(rule, ls, stepLaws, { search: false }) : proveRule(rule, ls, { search: false });
|
|
116
120
|
const out = stepLaws.length ? proveStepLaws(m, ls, stepLaws) : proveLaws(m, ls);
|
|
117
121
|
return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
|
|
118
122
|
};
|
|
@@ -245,16 +249,18 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
245
249
|
};
|
|
246
250
|
const choose = (k, of) => { let r = 1; for (let i = 0; i < k; i++)
|
|
247
251
|
r = r * (of - i) / (i + 1); return r; };
|
|
248
|
-
|
|
252
|
+
// A choice's index orders its values arbitrarily: it is a guard, or alone (a bound on it reads as the values it
|
|
253
|
+
// allows), never a term among others.
|
|
254
|
+
const plain = atoms.flatMap((a, i) => (a.cell ? [] : [i])), terms = plain.filter(i => !modeOf(i)?.choice), P = terms.length;
|
|
249
255
|
const scalarOk = (acc) => acc.some(([i]) => !atoms[i].param);
|
|
250
256
|
pick(1, [1], plain, scalarOk, false);
|
|
251
|
-
pick(2, choose(2, P) * 4 * mags.length ** 2 <= maxCandidates / 4 ? mags : [1],
|
|
257
|
+
pick(2, choose(2, P) * 4 * mags.length ** 2 <= maxCandidates / 4 ? mags : [1], terms, scalarOk, false);
|
|
252
258
|
if (choose(3, P) * 8 * mags.length ** 3 <= maxCandidates / 2)
|
|
253
|
-
pick(3, mags,
|
|
259
|
+
pick(3, mags, terms, scalarOk, false);
|
|
254
260
|
else if (choose(3, P) * 8 <= maxCandidates / 2)
|
|
255
|
-
pick(3, [1],
|
|
261
|
+
pick(3, [1], terms, scalarOk, false);
|
|
256
262
|
if (cellAtoms.length) {
|
|
257
|
-
const all = atoms.
|
|
263
|
+
const all = atoms.flatMap((_, i) => (modeOf(i)?.choice ? [] : [i])), cellOk = (acc) => acc.some(([i]) => atoms[i].cell) && acc.filter(([i]) => !atoms[i].cell).length <= 1;
|
|
258
264
|
pick(1, [1], cellAtoms, () => true, true);
|
|
259
265
|
pick(2, [1], all, cellOk, true);
|
|
260
266
|
if (choose(3, A) * 8 <= maxCandidates / 4)
|
|
@@ -329,7 +335,8 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
329
335
|
return `${nameOf(f.unreachable.atom)} is never ${shownValue(f.unreachable.atom, f.unreachable.value)}`;
|
|
330
336
|
const { l, r } = sides(f), within = allowedValues(f);
|
|
331
337
|
const body = within ? `${nameOf(f.coef[0][0])} is ${within.map(v => shownValue(f.coef[0][0], v)).join(' or ')}` : `${l} <= ${r}`;
|
|
332
|
-
const
|
|
338
|
+
const under = f.when ? (({ l: wl, r: wr }) => `${wl} <= ${wr} → `)(sides({ coef: f.when.coef, d: f.when.d, cells: false, size: 0 })) : '';
|
|
339
|
+
const guarded = !f.guard ? under + body : 'keyNot' in f.guard ? `k = ${nameOf(f.guard.keyNot)} or ${body}` : `${nameOf(f.guard.atom)} is ${shownValue(f.guard.atom, f.guard.value)} → ${under}${body}`;
|
|
333
340
|
return f.cells ? `every k: ${guarded}` : guarded;
|
|
334
341
|
};
|
|
335
342
|
const sum = (es) => es.reduce((p, q) => plus(p, q), n(0));
|
|
@@ -347,12 +354,13 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
347
354
|
if (f.unreachable)
|
|
348
355
|
return notAt(f.unreachable.atom, f.unreachable.value);
|
|
349
356
|
const lhs = sum([...partsOf(f, 1), ...(f.d < 0 ? [n(-f.d)] : [])]), rhs = sum([...partsOf(f, -1), ...(f.d > 0 ? [n(f.d)] : [])]);
|
|
350
|
-
|
|
357
|
+
// Under a condition Σ ≤ d: its negation, −Σ ≤ −d − 1, or the body.
|
|
358
|
+
const body = f.when ? any(condOf({ coef: f.when.coef.map(([i, c]) => [i, -c]), d: -f.when.d - 1, cells: false, size: 0 }), atMost(lhs, rhs)) : atMost(lhs, rhs);
|
|
351
359
|
return !f.guard ? body : 'keyNot' in f.guard ? any(same(x(KEY), x(atoms[f.guard.keyNot].parts[0][0])), body) : any(notAt(f.guard.atom, f.guard.value), body);
|
|
352
360
|
};
|
|
353
361
|
// The builder call, for a rule whose atoms are its own variables (naturals, or integers alone).
|
|
354
362
|
const sourceOf = (f, text) => {
|
|
355
|
-
if (!rule)
|
|
363
|
+
if (!rule || f.when)
|
|
356
364
|
return undefined;
|
|
357
365
|
const used = [...new Set([...f.coef.map(([i]) => i), ...(f.guard ? ['keyNot' in f.guard ? f.guard.keyNot : f.guard.atom] : []), ...(f.unreachable ? [f.unreachable.atom] : [])])];
|
|
358
366
|
const ints = f.coef.filter(([i]) => atoms[i].int).length;
|
|
@@ -388,6 +396,13 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
388
396
|
else if (valueOf(atoms[f.guard.atom], env) !== f.guard.value)
|
|
389
397
|
return true;
|
|
390
398
|
}
|
|
399
|
+
if (f.when) {
|
|
400
|
+
let w = 0;
|
|
401
|
+
for (const [i, c] of f.when.coef)
|
|
402
|
+
w += c * valueOf(atoms[i], env);
|
|
403
|
+
if (w > f.when.d)
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
391
406
|
let s = 0;
|
|
392
407
|
for (const [i, c] of f.coef)
|
|
393
408
|
s += c * valueOf(atoms[i], env);
|
|
@@ -489,6 +504,97 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
489
504
|
}
|
|
490
505
|
return pickF;
|
|
491
506
|
};
|
|
507
|
+
// A counterexample to induction no candidate rules out: forms false at it that every sample meets under a condition
|
|
508
|
+
// it meets too (one value, or a sum or difference of two, at most a threshold: 0, ±1, or its own value there), the
|
|
509
|
+
// choice (loc, say) at its value there as the guard. A condition no sample meets gives its negation instead. Only the
|
|
510
|
+
// eight values most unusual at it are read (furthest past the samples in its guard, then the rarest there).
|
|
511
|
+
const choiceMode = modes.find(mo => mo.choice && !atoms[mo.atom].cell);
|
|
512
|
+
const condAtoms = plain.filter(i => !atoms[i].param && i !== choiceMode?.atom);
|
|
513
|
+
const fromCti = (p) => {
|
|
514
|
+
const stop = Math.min(until, performance.now() + Math.max(300, (until - performance.now()) / 8));
|
|
515
|
+
const sc = p.scalar, pv = (coef) => { let v = 0; for (const [i, c] of coef)
|
|
516
|
+
v += c * valueOf(atoms[i], sc); return v; };
|
|
517
|
+
const gv = choiceMode ? valueOf(atoms[choiceMode.atom], sc) : undefined;
|
|
518
|
+
const inGuard = [];
|
|
519
|
+
for (let j = 0; j < NS; j++)
|
|
520
|
+
if (gv === undefined || valsS[choiceMode.atom][j] === gv)
|
|
521
|
+
inGuard.push(j);
|
|
522
|
+
const unusual = (i) => {
|
|
523
|
+
const v = valueOf(atoms[i], sc);
|
|
524
|
+
let lo = Infinity, hi = -Infinity, same = 0;
|
|
525
|
+
for (const j of inGuard) {
|
|
526
|
+
const w = valsS[i][j];
|
|
527
|
+
if (w < lo)
|
|
528
|
+
lo = w;
|
|
529
|
+
if (w > hi)
|
|
530
|
+
hi = w;
|
|
531
|
+
if (w === v)
|
|
532
|
+
same++;
|
|
533
|
+
}
|
|
534
|
+
return [Math.max(0, lo - v, v - hi), -same];
|
|
535
|
+
};
|
|
536
|
+
const focus = condAtoms.map(i => ({ i, u: unusual(i) })).sort((a, b) => b.u[0] - a.u[0] || b.u[1] - a.u[1]).slice(0, 8).map(x => x.i);
|
|
537
|
+
// Over the focus: one or two values with the magnitudes, three with ±1.
|
|
538
|
+
const bodies = [];
|
|
539
|
+
const signed = (ks) => ks.flatMap(k => [k, -k]);
|
|
540
|
+
for (const [a, i] of focus.entries()) {
|
|
541
|
+
bodies.push({ coef: [[i, 1]] }, { coef: [[i, -1]] });
|
|
542
|
+
for (const [b2, k] of focus.slice(a + 1).entries()) {
|
|
543
|
+
for (const c1 of signed(mags))
|
|
544
|
+
for (const c2 of signed(mags))
|
|
545
|
+
if (gcd(c1, c2) === 1)
|
|
546
|
+
bodies.push({ coef: [[i, c1], [k, c2]] });
|
|
547
|
+
for (const q of focus.slice(a + b2 + 2))
|
|
548
|
+
for (const c1 of [1, -1])
|
|
549
|
+
for (const c2 of [1, -1])
|
|
550
|
+
for (const c3 of [1, -1])
|
|
551
|
+
bodies.push({ coef: [[i, c1], [k, c2], [q, c3]] });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const conds = [];
|
|
555
|
+
for (const [a, i] of focus.entries()) {
|
|
556
|
+
conds.push([[i, 1]], [[i, -1]]);
|
|
557
|
+
for (const k of focus.slice(a + 1))
|
|
558
|
+
conds.push([[i, 1], [k, -1]], [[i, -1], [k, 1]], [[i, 1], [k, 1]]);
|
|
559
|
+
}
|
|
560
|
+
const out = [], guard = choiceMode ? { atom: choiceMode.atom, value: gv } : undefined;
|
|
561
|
+
for (const coef of conds) {
|
|
562
|
+
const here = pv(coef);
|
|
563
|
+
for (const d of [...new Set([here, 0, 1, -1])].filter(d => here <= d)) {
|
|
564
|
+
if (performance.now() > stop)
|
|
565
|
+
break;
|
|
566
|
+
const rows = [];
|
|
567
|
+
for (const j of inGuard)
|
|
568
|
+
if (at(coef, valsS, j) <= d)
|
|
569
|
+
rows.push(j);
|
|
570
|
+
const when = { coef, d }, base = sizeOf(coef) + 80;
|
|
571
|
+
if (!rows.length) {
|
|
572
|
+
out.push({ coef: coef.map(([i, c]) => [i, -c]), d: -d - 1, cells: false, ...(guard ? { guard } : {}), size: base });
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const stride = Math.max(1, Math.ceil(rows.length / 600));
|
|
576
|
+
for (const [q, sh] of bodies.entries()) {
|
|
577
|
+
if (q % 256 === 0 && performance.now() > stop)
|
|
578
|
+
break;
|
|
579
|
+
const v = pv(sh.coef);
|
|
580
|
+
let most = -Infinity, ok = true;
|
|
581
|
+
for (let t = 0; t < rows.length; t += stride) {
|
|
582
|
+
const w = at(sh.coef, valsS, rows[t]);
|
|
583
|
+
if (w >= v) {
|
|
584
|
+
ok = false;
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
if (w > most)
|
|
588
|
+
most = w;
|
|
589
|
+
}
|
|
590
|
+
if (ok && Number.isFinite(most))
|
|
591
|
+
out.push({ coef: sh.coef, d: most, cells: false, ...(guard ? { guard } : {}), when, size: base + sizeOf(sh.coef) });
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// A stride may skip a sample that breaks a form: each is checked again at every sample in its guard.
|
|
596
|
+
return out.sort((x, y) => x.size - y.size).slice(0, 2000).filter(f => inGuard.every(j => holdsRow(f, rowsS[j]))).slice(0, 400);
|
|
597
|
+
};
|
|
492
598
|
// The search proves one law at a time (proveLaw), each with every law assumed, and keeps what it proved while laws
|
|
493
599
|
// are only added (more laws assumed keep a proof a proof); dropping one forgets it all. Once every law is proven so,
|
|
494
600
|
// the set is proven as a whole, by the rule's own prover: that verdict is the answer.
|
|
@@ -545,7 +651,13 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
545
651
|
const p = /@step|@\d+ steps/.test(last.goal) && last.witness ? witnessPoint(last.witness) : undefined;
|
|
546
652
|
if (p)
|
|
547
653
|
ctis.push(p);
|
|
548
|
-
|
|
654
|
+
let next = p ? best(p) : undefined;
|
|
655
|
+
if (!next && p) {
|
|
656
|
+
const t = performance.now(), made = fromCti(p);
|
|
657
|
+
pool.push(...made);
|
|
658
|
+
next = best(p);
|
|
659
|
+
opts.progress?.('cti', { forms: made.length, ms: Math.round(performance.now() - t) });
|
|
660
|
+
}
|
|
549
661
|
if (next) {
|
|
550
662
|
chosen.push(next);
|
|
551
663
|
continue;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { C, E } from '../core/machine.js';
|
|
2
|
+
import { type Goal, type Tree } from './kernel.js';
|
|
3
|
+
export type Fact = {
|
|
4
|
+
name: string;
|
|
5
|
+
keys: string[];
|
|
6
|
+
holds: C;
|
|
7
|
+
};
|
|
8
|
+
export type Use = {
|
|
9
|
+
node: number;
|
|
10
|
+
subst: Record<string, E>;
|
|
11
|
+
guard: C;
|
|
12
|
+
premises: Tree[];
|
|
13
|
+
descent: Tree;
|
|
14
|
+
};
|
|
15
|
+
export type CNode = {
|
|
16
|
+
name: string;
|
|
17
|
+
hyps: C[];
|
|
18
|
+
concl: C;
|
|
19
|
+
rank: E[];
|
|
20
|
+
insts: {
|
|
21
|
+
fact: number;
|
|
22
|
+
at: E[];
|
|
23
|
+
}[];
|
|
24
|
+
uses: Use[];
|
|
25
|
+
main: Tree;
|
|
26
|
+
};
|
|
27
|
+
export type Cyclic = {
|
|
28
|
+
facts: Fact[];
|
|
29
|
+
nodes: CNode[];
|
|
30
|
+
};
|
|
31
|
+
export declare const falseC: C;
|
|
32
|
+
export declare const lexLt: (as: E[], bs: E[]) => C;
|
|
33
|
+
export declare const instHyps: (facts: Fact[], insts: CNode["insts"]) => C[];
|
|
34
|
+
export declare const baseHyps: (facts: Fact[], n: Pick<CNode, "hyps" | "insts">) => C[];
|
|
35
|
+
export declare const useHyps: (nodes: Pick<CNode, "concl">[], uses: Pick<Use, "node" | "subst" | "guard">[]) => C[];
|
|
36
|
+
type Shape = Pick<CNode, 'name' | 'hyps' | 'concl' | 'rank' | 'insts'> & {
|
|
37
|
+
uses: Pick<Use, 'node' | 'subst' | 'guard'>[];
|
|
38
|
+
};
|
|
39
|
+
export declare const mainGoal: (facts: Fact[], nodes: Shape[], n: Shape) => Goal;
|
|
40
|
+
export declare const premiseGoal: (facts: Fact[], n: Shape, u: Shape["uses"][number], c: C, i: number) => Goal;
|
|
41
|
+
export declare const descentGoal: (facts: Fact[], n: Shape, u: Shape["uses"][number], m: Shape) => Goal;
|
|
42
|
+
export declare const factsAvoid: (facts: Fact[], subst: Record<string, E>) => boolean;
|
|
43
|
+
export declare function refuseCyclic(p: Cyclic): string | undefined;
|
|
44
|
+
export declare const checkCyclic: (p: Cyclic) => boolean;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { check, substC, substE } from "./kernel.js";
|
|
2
|
+
const zero = { op: 'const', value: 0 };
|
|
3
|
+
export const falseC = { op: '<', a: zero, b: zero };
|
|
4
|
+
// as below bs in order: the first place they differ, as's is smaller (Lean's lexLtC, built the same way).
|
|
5
|
+
export const lexLt = (as, bs) => (as.length && bs.length
|
|
6
|
+
? { op: 'or', xs: [{ op: '<', a: as[0], b: bs[0] }, { op: 'and', xs: [{ op: '=', a: as[0], b: bs[0] }, lexLt(as.slice(1), bs.slice(1))] }] }
|
|
7
|
+
: falseC);
|
|
8
|
+
const sub = (s) => ({ vars: s, maps: {}, flags: {} });
|
|
9
|
+
// A fact at the terms a node names: its keys paired with the terms (the shorter list decides, as Lean's zip does).
|
|
10
|
+
const keyed = (keys, at) => Object.fromEntries(keys.slice(0, at.length).map((key, i) => [key, at[i]]));
|
|
11
|
+
export const instHyps = (facts, insts) => insts.flatMap(p => { const f = facts[p.fact]; return f ? [substC(f.holds, sub(keyed(f.keys, p.at)))] : []; });
|
|
12
|
+
export const baseHyps = (facts, n) => [...n.hyps, ...instHyps(facts, n.insts)];
|
|
13
|
+
export const useHyps = (nodes, uses) => uses.flatMap(u => {
|
|
14
|
+
const m = nodes[u.node];
|
|
15
|
+
return m ? [{ op: 'or', xs: [{ op: 'not', x: u.guard }, substC(m.concl, sub(u.subst))] }] : [];
|
|
16
|
+
});
|
|
17
|
+
export const mainGoal = (facts, nodes, n) => ({ name: `${n.name}`, hyps: [...baseHyps(facts, n), ...useHyps(nodes, n.uses), { op: 'not', x: n.concl }] });
|
|
18
|
+
export const premiseGoal = (facts, n, u, c, i) => ({ name: `${n.name}: use of node ${u.node}, its hypothesis ${i}`, hyps: [...baseHyps(facts, n), u.guard, { op: 'not', x: substC(c, sub(u.subst)) }] });
|
|
19
|
+
export const descentGoal = (facts, n, u, m) => ({ name: `${n.name}: use of node ${u.node}, its rank below`, hyps: [...baseHyps(facts, n), u.guard, { op: 'not', x: lexLt(m.rank.map(e => substE(e, sub(u.subst))), n.rank) }] });
|
|
20
|
+
const occurs = (name, t) => JSON.stringify(t).includes(`"op":"var","name":${JSON.stringify(name)}`);
|
|
21
|
+
// The language Lean's twin has: no flag, no quantifier.
|
|
22
|
+
const plain = (t) => !/"op":"(?:flag|all)"/.test(JSON.stringify(t));
|
|
23
|
+
export const factsAvoid = (facts, subst) => Object.keys(subst).every(v => facts.every(f => f.keys.includes(v) || !occurs(v, f.holds)));
|
|
24
|
+
// Why a cyclic proof is refused, or undefined when it checks.
|
|
25
|
+
export function refuseCyclic(p) {
|
|
26
|
+
const { facts, nodes } = p;
|
|
27
|
+
if (!plain(facts) || !plain(nodes.map(n => [n.hyps, n.concl, n.rank, n.insts, n.uses.map(u => [u.subst, u.guard])])))
|
|
28
|
+
return 'a flag or a quantifier (no cyclic proof reads one)';
|
|
29
|
+
for (const f of facts)
|
|
30
|
+
if (new Set(f.keys).size !== f.keys.length)
|
|
31
|
+
return `fact ${f.name}: a key twice`;
|
|
32
|
+
for (const n of nodes) {
|
|
33
|
+
if (!check(mainGoal(facts, nodes, n), n.main))
|
|
34
|
+
return `${n.name}: its goal`;
|
|
35
|
+
for (const [j, u] of n.uses.entries()) {
|
|
36
|
+
const m = nodes[u.node];
|
|
37
|
+
if (!m)
|
|
38
|
+
return `${n.name}: use ${j} names no node`;
|
|
39
|
+
if (m.rank.length !== n.rank.length)
|
|
40
|
+
return `${n.name}: use ${j} compares ranks of different lengths`;
|
|
41
|
+
if (!factsAvoid(facts, u.subst))
|
|
42
|
+
return `${n.name}: use ${j} assigns a variable a fact reads`;
|
|
43
|
+
if (m.hyps.length !== u.premises.length)
|
|
44
|
+
return `${n.name}: use ${j} has ${u.premises.length} premise trees for ${m.hyps.length} hypotheses`;
|
|
45
|
+
for (const [i, c] of m.hyps.entries())
|
|
46
|
+
if (!check(premiseGoal(facts, n, u, c, i), u.premises[i]))
|
|
47
|
+
return `${n.name}: use ${j}, hypothesis ${i} of ${m.name}`;
|
|
48
|
+
if (!check(descentGoal(facts, n, u, m), u.descent))
|
|
49
|
+
return `${n.name}: use ${j} doesn't lower the rank`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
export const checkCyclic = (p) => refuseCyclic(p) === undefined;
|
package/dist/kernel/prove.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export declare const solveKinds: Record<string, {
|
|
|
21
21
|
ms: number;
|
|
22
22
|
rows: number;
|
|
23
23
|
}>;
|
|
24
|
+
export declare let proveDump: ((goal: Goal) => void) | undefined;
|
|
25
|
+
export declare const setProveDump: (f: ((goal: Goal) => void) | undefined) => void;
|
|
26
|
+
export declare const cellProducts: {
|
|
27
|
+
on: boolean;
|
|
28
|
+
};
|
|
24
29
|
export type Outcome = {
|
|
25
30
|
tree: Tree;
|
|
26
31
|
nodes: number;
|
|
@@ -30,10 +35,15 @@ export type Outcome = {
|
|
|
30
35
|
path: Path;
|
|
31
36
|
why?: string;
|
|
32
37
|
};
|
|
38
|
+
export declare function withDeadline<T>(until: number, f: () => T): T;
|
|
33
39
|
export declare function grow(goal: Goal, path?: Path, budget?: {
|
|
34
40
|
nodes: number;
|
|
35
41
|
until?: number;
|
|
42
|
+
units?: boolean;
|
|
36
43
|
}): Outcome;
|
|
44
|
+
export declare const minimal: {
|
|
45
|
+
on: boolean;
|
|
46
|
+
};
|
|
37
47
|
export type Report = {
|
|
38
48
|
proven: boolean;
|
|
39
49
|
nodes: number;
|