gdp-wired 0.19.0 → 0.21.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 +37 -6
- package/dist/core/cycles.d.ts +35 -2
- package/dist/core/cycles.js +458 -51
- package/dist/core/cyclic.d.ts +18 -4
- package/dist/core/cyclic.js +319 -263
- package/dist/core/data.d.ts +196 -15
- package/dist/core/data.js +1284 -213
- package/dist/core/discover.js +54 -33
- package/dist/core/dsl.d.ts +31 -3
- package/dist/core/dsl.js +168 -38
- package/dist/core/exists.d.ts +26 -0
- package/dist/core/exists.js +625 -0
- package/dist/core/infer.d.ts +13 -0
- package/dist/core/infer.js +258 -41
- package/dist/core/machine.d.ts +27 -0
- package/dist/core/machine.js +45 -0
- package/dist/core/solver.d.ts +3 -0
- package/dist/core/solver.js +5 -0
- package/dist/core/transfer.d.ts +17 -0
- package/dist/core/transfer.js +468 -0
- package/dist/core/translate.js +10 -4
- package/dist/core/watch.js +5 -1
- package/dist/kernel/prove.d.ts +8 -1
- package/dist/kernel/prove.js +106 -55
- package/dist/lean-kernel/emit.d.ts +4 -1
- package/dist/lean-kernel/emit.js +336 -69
- package/dist/lean-kernel/library.js +2 -2
- package/package.json +2 -2
package/dist/core/dsl.js
CHANGED
|
@@ -15,14 +15,15 @@
|
|
|
15
15
|
//
|
|
16
16
|
// A step returns its updates as one object: every right-hand side reads the state before the step, as the kernel and
|
|
17
17
|
// both runtimes do.
|
|
18
|
-
import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf, pairedArgs } from "./machine.js";
|
|
18
|
+
import { carriedView, compileFunction, divName, divisor, divisorsIn, evalC, evalE, interpret, isArithmetic, loadJs, modName, numbersOf, pairedArgs, withArithmetic } from "./machine.js";
|
|
19
19
|
import { allowed, keysOf, requestEnv, watch } from "./watch.js";
|
|
20
|
+
import { someHolds, substVars } from "./exists.js";
|
|
20
21
|
import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
|
|
21
22
|
import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws, usualProof, withDeadline } from "../kernel/prove.js";
|
|
22
|
-
import { certCyclic, certOn, crossCyclic, cyclicOn,
|
|
23
|
+
import { certCyclic, certOn, crossCyclic, cyclicOn, foldNodes, proveCyclic, quietly, recurNodes, translatedProof, uncovered } from "./cyclic.js";
|
|
23
24
|
import { discover } from "./discover.js";
|
|
24
25
|
import { inferLaws } from "./infer.js";
|
|
25
|
-
import { cycleLemmas, findRank } from "./cycles.js";
|
|
26
|
+
import { cycleLemmas, findLeadsTo, findRank } from "./cycles.js";
|
|
26
27
|
// --- terms and conditions -------------------------------------------------------------------------------------------
|
|
27
28
|
const naturalConst = (v) => {
|
|
28
29
|
if (!Number.isSafeInteger(v) || v < 0)
|
|
@@ -53,6 +54,11 @@ export class Term {
|
|
|
53
54
|
minus(b) { return new Term({ op: 'sub', a: this.e, b: termOf(b) }, both(this.val, valOf(b), (x, y) => Math.max(0, x - y))); }
|
|
54
55
|
times(k) { return new Term({ op: 'mul', k: naturalConst(k), a: this.e }, both(this.val, k, (x, y) => x * y)); }
|
|
55
56
|
mul(b) { return new Term({ op: 'prod', a: this.e, b: termOf(b) }, both(this.val, valOf(b), (x, y) => x * y)); }
|
|
57
|
+
// x div k and x mod k, rounded down, for a constant k >= 1: the rule's functions div$k and mod$k at x, which the
|
|
58
|
+
// kernel knows by x = k·(x div k) + x mod k and x mod k < k (machine.ts's arithmetic). Division by a term that isn't a
|
|
59
|
+
// constant has no such fact in linear arithmetic (x = y·q + r is a product of two unknowns), so it isn't offered.
|
|
60
|
+
modBy(k) { divisor(k, 'modBy'); return new Term({ op: 'get', map: modName(k), key: this.e }, this.val === undefined ? undefined : this.val % k); }
|
|
61
|
+
divBy(k) { divisor(k, 'divBy'); return new Term({ op: 'get', map: divName(k), key: this.e }, this.val === undefined ? undefined : Math.floor(this.val / k)); }
|
|
56
62
|
lt(b) { return new Cond({ op: '<', a: this.e, b: termOf(b) }, both(this.val, valOf(b), (x, y) => x < y)); }
|
|
57
63
|
atMost(b) { return new Cond({ op: '<=', a: this.e, b: termOf(b) }, both(this.val, valOf(b), (x, y) => x <= y)); }
|
|
58
64
|
gt(b) { return new Cond({ op: '<', a: termOf(b), b: this.e }, both(this.val, valOf(b), (x, y) => x > y)); }
|
|
@@ -67,21 +73,26 @@ const known = (xs) => (xs.every(x => x !== undefined) ? xs : undefined);
|
|
|
67
73
|
export class Cond {
|
|
68
74
|
c;
|
|
69
75
|
ok;
|
|
70
|
-
|
|
76
|
+
ex;
|
|
77
|
+
// `ex`: the existentials (exists) inside, each with whether it reads as "there exists" where it sits (under no not, or
|
|
78
|
+
// an even number of them): a law, the assumption or a precondition pulls each such one out as ∃ before the whole.
|
|
79
|
+
constructor(c, ok, ex) {
|
|
71
80
|
this.c = c;
|
|
72
81
|
this.ok = ok;
|
|
82
|
+
this.ex = ex;
|
|
73
83
|
}
|
|
74
84
|
and(...xs) { return all(this, ...xs); }
|
|
75
85
|
or(...xs) { return any(this, ...xs); }
|
|
76
|
-
not() { return new Cond({ op: 'not', x: this.c }, this.ok === undefined ? undefined : !this.ok); }
|
|
86
|
+
not() { return new Cond({ op: 'not', x: this.c }, this.ok === undefined ? undefined : !this.ok, this.ex?.map(b => ({ ...b, pos: !b.pos }))); }
|
|
77
87
|
implies(b) { return any(this.not(), b); }
|
|
78
88
|
iff(b) { return all(this.implies(b), b.implies(this)); }
|
|
79
89
|
}
|
|
80
90
|
export const lit = (v) => new Term(termOf(v), v);
|
|
81
91
|
export const yes = new Cond({ op: '<=', a: { op: 'const', value: 0 }, b: { op: 'const', value: 0 } }, true);
|
|
82
92
|
export const no = new Cond({ op: '<', a: { op: 'const', value: 0 }, b: { op: 'const', value: 0 } }, false);
|
|
83
|
-
|
|
84
|
-
export const
|
|
93
|
+
const exOf = (cs) => (cs.some(c => c.ex?.length) ? cs.flatMap(c => c.ex ?? []) : undefined);
|
|
94
|
+
export const all = (...cs) => (cs.length === 0 ? yes : cs.length === 1 ? cs[0] : new Cond({ op: 'and', xs: cs.map(c => c.c) }, known(cs.map(c => c.ok))?.every(x => x), exOf(cs)));
|
|
95
|
+
export const any = (...cs) => (cs.length === 0 ? no : cs.length === 1 ? cs[0] : new Cond({ op: 'or', xs: cs.map(c => c.c) }, known(cs.map(c => c.ok))?.some(x => x), exOf(cs)));
|
|
85
96
|
export const not = (c) => c.not();
|
|
86
97
|
export const min = (a, b) => new Term({ op: 'min', a: termOf(a), b: termOf(b) }, both(valOf(a), valOf(b), Math.min));
|
|
87
98
|
export const max = (a, b) => new Term({ op: 'max', a: termOf(a), b: termOf(b) }, both(valOf(a), valOf(b), Math.max));
|
|
@@ -244,6 +255,11 @@ export class Int {
|
|
|
244
255
|
min(b) { return when(this.atMost(b), this, b); }
|
|
245
256
|
max(b) { return when(this.atLeast(b), this, b); }
|
|
246
257
|
get isNegative() { return this.neg.gt(this.pos); }
|
|
258
|
+
// x mod k and x div k for a constant k >= 1, as SMT-LIB and Lean's Int read them for k > 0: x = k·(x div k) + x mod k
|
|
259
|
+
// with 0 <= x mod k < k (so -7 div 2 is -4 and -7 mod 2 is 1). Over the naturals: pos + (k − 1)·neg is x + k·neg,
|
|
260
|
+
// the same remainder, and its quotient is x div k + neg, so both are the natural division of that one natural.
|
|
261
|
+
modBy(k) { divisor(k, 'modBy'); return add(this.pos, this.neg.times(k - 1)).modBy(k); }
|
|
262
|
+
divBy(k) { divisor(k, 'divBy'); return new Int(add(this.pos, this.neg.times(k - 1)).divBy(k), this.neg); }
|
|
247
263
|
// |value|, and the value where it is not negative (0 below).
|
|
248
264
|
get abs() { return this.pos.minus(this.neg).plus(this.neg.minus(this.pos)); }
|
|
249
265
|
get clamped() { return this.pos.minus(this.neg); }
|
|
@@ -808,6 +824,32 @@ export const someKey = (f, witness) => {
|
|
|
808
824
|
const none = everyOk(scope?.keys, k => f(keyTerm(k)).not().ok);
|
|
809
825
|
return new Cond({ op: 'not', x: { op: 'all', key, c: f(v(key)).not().c, ...(witness === undefined ? {} : { witness: termOf(witness) }) } }, none === undefined ? undefined : !none);
|
|
810
826
|
};
|
|
827
|
+
export function exists(a, b, c) {
|
|
828
|
+
const [kind, f, w] = (a instanceof Kind ? [a, b, c] : [nat, a, b]);
|
|
829
|
+
const name = `some$${++boundCounter}`, value = kind.value(name), body = all(...kind.assume(name), f(value));
|
|
830
|
+
const store = (x) => Object.fromEntries(Object.entries(kind.store(name, x)).map(([n, t]) => [n, termOf(t)]));
|
|
831
|
+
const ghost = typeof w === 'object' && w !== null && 'step' in w && 'start' in w && typeof w.step === 'function';
|
|
832
|
+
const given = w === undefined ? {} : ghost
|
|
833
|
+
? { start: store(w.start), step: (env) => store(w.step(value, env)) }
|
|
834
|
+
: { terms: store(w) };
|
|
835
|
+
return new Cond(body.c, undefined, [...(body.ex ?? []), { vars: kind.vars(name), body: body.c, pos: true, ...given }]);
|
|
836
|
+
}
|
|
837
|
+
// A condition's existentials pulled out before it (each reading as "there exists"), their naturals renamed `${prefix}0`,
|
|
838
|
+
// `${prefix}1`, ... (an integer's second part keeping $neg), so a machine built again names them alike.
|
|
839
|
+
function pulledOut(cs, what, prefix) {
|
|
840
|
+
const bs = cs.flatMap(c => c.ex ?? []);
|
|
841
|
+
if (bs.some(b => !b.pos))
|
|
842
|
+
throw new Error(`${what}: exists(...) under a not (or on the left of implies) says "for every value"; state it as a law about every value of a param instead`);
|
|
843
|
+
const ren = {};
|
|
844
|
+
if (prefix !== undefined)
|
|
845
|
+
bs.forEach((b, i) => b.vars.forEach(n => { ren[n] = { op: 'var', name: n.endsWith('$neg') ? `${prefix}${i}$neg` : `${prefix}${i}` }; }));
|
|
846
|
+
const r = (c) => (prefix === undefined ? c : substVars(c, ren));
|
|
847
|
+
const vars = bs.flatMap(b => b.vars.map(n => (prefix === undefined ? n : ren[n].name)));
|
|
848
|
+
const out = cs.map(c => r(c.c)), stray = out.flatMap(c => namesIn(c)).filter(n => n.startsWith('some$') && !vars.includes(n) && !/^some\$[ap]\d/.test(n));
|
|
849
|
+
if (stray.length)
|
|
850
|
+
throw new Error(`${what}: ${stray[0]} is bound by an exists(...) whose condition was taken apart; keep it whole (and, or, not, implies)`);
|
|
851
|
+
return { cs: out, vars, bodies: bs.map(b => ({ vars: b.vars.map(n => (prefix === undefined ? n : ren[n].name)), body: r(b.body) })) };
|
|
852
|
+
}
|
|
811
853
|
// A map from naturals to naturals, every cell starting at `init`. One write per map per step.
|
|
812
854
|
export class MapRef {
|
|
813
855
|
name;
|
|
@@ -1430,8 +1472,12 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1430
1472
|
lemmaProof = usualProof.last;
|
|
1431
1473
|
if (cyclicOn())
|
|
1432
1474
|
crossCyclic('recurrence', name, verdict.proven && verdict.checked, () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0, translatedRecur({ lemmas: lemmaProof }));
|
|
1433
|
-
|
|
1434
|
-
|
|
1475
|
+
// (the usual trees checked already, inside quietly: where no cyclic proof is made they are the certificate)
|
|
1476
|
+
if (certOn() && verdict.proven && verdict.checked) {
|
|
1477
|
+
const c = certCyclic('recurrence', name, translatedRecur({ lemmas: lemmaProof }), () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0);
|
|
1478
|
+
if (!c.checked)
|
|
1479
|
+
uncovered('recurrence', name, c.why);
|
|
1480
|
+
}
|
|
1435
1481
|
failed('lemmas', verdict);
|
|
1436
1482
|
proofs.lemmas = { machine: r.machine, laws };
|
|
1437
1483
|
}
|
|
@@ -1472,8 +1518,11 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1472
1518
|
const t0 = performance.now(), v = quietly(() => proveRule(r, laws, { search: false })), usualMs = performance.now() - t0, betweenProof = usualProof.last;
|
|
1473
1519
|
if (cyclicOn())
|
|
1474
1520
|
crossCyclic('recurrence, between', name, v.proven && v.checked, () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs, betweenProof && translatedRecur({ lemmas: lemmaProof, between: betweenProof }));
|
|
1475
|
-
if (certOn() && v.proven && v.checked)
|
|
1476
|
-
|
|
1521
|
+
if (certOn() && v.proven && v.checked) {
|
|
1522
|
+
const c = certCyclic('recurrence, between', name, betweenProof && translatedRecur({ lemmas: lemmaProof, between: betweenProof }), () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs);
|
|
1523
|
+
if (!c.checked)
|
|
1524
|
+
uncovered('recurrence, between', name, c.why);
|
|
1525
|
+
}
|
|
1477
1526
|
failed('laws between two points', v);
|
|
1478
1527
|
proofs.between = { machine: r.machine, laws };
|
|
1479
1528
|
verdict = { proven: true, checked: verdict.checked && v.checked };
|
|
@@ -1565,6 +1614,8 @@ const fnValueOf = (d) => {
|
|
|
1565
1614
|
};
|
|
1566
1615
|
let keyCounter = 0;
|
|
1567
1616
|
const sources = new WeakMap();
|
|
1617
|
+
// A rule's existentials in its precondition and assumption (core/exists.ts): what encodeParams and encodeInput find.
|
|
1618
|
+
const skolemsOf = new WeakMap();
|
|
1568
1619
|
const withSource = (x, src) => (sources.set(x, src), x);
|
|
1569
1620
|
// A k-step law's later requests are built over marked names (j:name, which no rule can declare: ':' is reserved); the
|
|
1570
1621
|
// kernel reads them as name@j.
|
|
@@ -1761,11 +1812,37 @@ export class Rule {
|
|
|
1761
1812
|
const key = `key$${++keyCounter}`;
|
|
1762
1813
|
if (out instanceof Cond) {
|
|
1763
1814
|
const p = prenex(out.c, `law ${name}`, key);
|
|
1764
|
-
return p ? { name, forall: key, holds: p } : { name, holds: out.c };
|
|
1815
|
+
return this.divides(this.someOf(name, out, p ? { name, forall: key, holds: p } : { name, holds: out.c }));
|
|
1765
1816
|
}
|
|
1766
|
-
const [k1, more] = keysFor(out),
|
|
1767
|
-
return more.length ? { name, forall: k1, keys: more, holds } : { name, forall: k1, holds: prenex(holds, `law ${name}`, k1) ?? holds };
|
|
1768
|
-
}
|
|
1817
|
+
const [k1, more] = keysFor(out), inner = out.forall(v(k1), ...more.map(v)), holds = inner.c;
|
|
1818
|
+
return this.divides(this.someOf(name, inner, more.length ? { name, forall: k1, keys: more, holds } : { name, forall: k1, holds: prenex(holds, `law ${name}`, k1) ?? holds }));
|
|
1819
|
+
}
|
|
1820
|
+
// A law's existentials (exists), pulled out before it: `some` its naturals, `witness` the ones given (a ghost's step
|
|
1821
|
+
// read with the step's own values).
|
|
1822
|
+
someOf(name, out, law) {
|
|
1823
|
+
if (!out.ex?.length)
|
|
1824
|
+
return law;
|
|
1825
|
+
const { vars } = pulledOut([out], `law ${name}`), bs = out.ex;
|
|
1826
|
+
const terms = Object.assign({}, ...bs.map(b => b.terms ?? {}));
|
|
1827
|
+
const ghost = {};
|
|
1828
|
+
const env = bs.some(b => b.step) ? this.env({}) : {};
|
|
1829
|
+
for (const b of bs)
|
|
1830
|
+
if (b.start && b.step) {
|
|
1831
|
+
const st = b.step(env);
|
|
1832
|
+
for (const n of b.vars)
|
|
1833
|
+
ghost[n] = { start: b.start[n] ?? { op: 'const', value: 0 }, step: st[n] ?? { op: 'var', name: n } };
|
|
1834
|
+
}
|
|
1835
|
+
const witness = { ...(Object.keys(terms).length ? { terms } : {}), ...(Object.keys(ghost).length ? { ghost } : {}) };
|
|
1836
|
+
return { ...law, some: vars, ...(Object.keys(witness).length ? { witness } : {}) };
|
|
1837
|
+
}
|
|
1838
|
+
// What a law, a step law or a decision divides by, noted so the machine declares it (Spec.arith).
|
|
1839
|
+
divides(x) { if (this.spec.arith)
|
|
1840
|
+
divisorsIn([x], this.spec.arith); return x; }
|
|
1841
|
+
// Divide by these too (inferLaws' candidates): the machine declares div$k and mod$k for each.
|
|
1842
|
+
dividesBy(...ks) { for (const k of ks)
|
|
1843
|
+
(this.spec.arith ??= new Set()).add(divisor(k, 'dividesBy')); return this; }
|
|
1844
|
+
// The same rule dividing by these too, its own divisors left as they are.
|
|
1845
|
+
withDivisors(...ks) { return this.with({ arith: new Set([...(this.spec.arith ?? []), ...ks.map(k => divisor(k, 'withDivisors'))]) }); }
|
|
1769
1846
|
// What the kinds keep true of the state and of the maps and lists of a kind (Kind.invariant: a flag 0 or 1, a choice or
|
|
1770
1847
|
// a case in range, a presence bit 0 or 1), as laws: proveRule, proveSteps and proveLeadsTo prove them beside the laws
|
|
1771
1848
|
// they are given, so a law may lean on "the hold is idle, holding or released" without stating it; give them to
|
|
@@ -1798,10 +1875,10 @@ export class Rule {
|
|
|
1798
1875
|
const key = `key$${++keyCounter}`, src = { step: { then: then, ...(when ? { when: when } : {}) } };
|
|
1799
1876
|
if (out instanceof Cond) {
|
|
1800
1877
|
const p = prenex(out.c, `step law ${name}`, key);
|
|
1801
|
-
return withSource(p ? { name, forall: key, then: p, ...w } : { name, then: out.c, ...w }, src);
|
|
1878
|
+
return withSource(this.divides(p ? { name, forall: key, then: p, ...w } : { name, then: out.c, ...w }), src);
|
|
1802
1879
|
}
|
|
1803
1880
|
const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
|
|
1804
|
-
return withSource(more.length ? { name, forall: k1, keys: more, then: c, ...w } : { name, forall: k1, then: prenex(c, `step law ${name}`, k1) ?? c, ...w }, src);
|
|
1881
|
+
return withSource(this.divides(more.length ? { name, forall: k1, keys: more, then: c, ...w } : { name, forall: k1, then: prenex(c, `step law ${name}`, k1) ?? c, ...w }), src);
|
|
1805
1882
|
}
|
|
1806
1883
|
// A law about k steps in a row (k from 2 to 64): `then(first, last, requests)` relates what the first step reads to the
|
|
1807
1884
|
// state, maps and sums after the last, with each step's request and queries (requests[0] is the first's); `when(before,
|
|
@@ -1825,10 +1902,10 @@ export class Rule {
|
|
|
1825
1902
|
const key = `key$${++keyCounter}`;
|
|
1826
1903
|
if (out instanceof Cond) {
|
|
1827
1904
|
const p = prenex(out.c, `step law ${name}`, key);
|
|
1828
|
-
return p ? { name, steps: k, forall: key, then: marked(p), ...w } : { name, steps: k, then: marked(out.c), ...w };
|
|
1905
|
+
return this.divides(p ? { name, steps: k, forall: key, then: marked(p), ...w } : { name, steps: k, then: marked(out.c), ...w });
|
|
1829
1906
|
}
|
|
1830
1907
|
const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
|
|
1831
|
-
return more.length ? { name, steps: k, forall: k1, keys: more, then: marked(c), ...w } : { name, steps: k, forall: k1, then: marked(prenex(c, `step law ${name}`, k1) ?? c), ...w };
|
|
1908
|
+
return this.divides(more.length ? { name, steps: k, forall: k1, keys: more, then: marked(c), ...w } : { name, steps: k, forall: k1, then: marked(prenex(c, `step law ${name}`, k1) ?? c), ...w });
|
|
1832
1909
|
}
|
|
1833
1910
|
// "Whenever `from` holds, `to` holds later", in every run that takes each helpful request infinitely often: proven by
|
|
1834
1911
|
// a rank (a natural over the state and params, or several compared in order) that no step raises while `from` holds
|
|
@@ -1839,12 +1916,13 @@ export class Rule {
|
|
|
1839
1916
|
// rank: ({t, served}) => t.plus(1).minus(served), helpful: () => ({op: 1})})
|
|
1840
1917
|
// batches.leadsTo('every batch is done', {from: () => yes, to: ({waiting, items}) => waiting.eq(0).and(items.eq(0)),
|
|
1841
1918
|
// rank: ({waiting, items}) => [waiting, items], helpful: [{when: ({items}) => items.gt(0), request: () => ({op: 1})}, {when: ({items}) => items.eq(0), request: () => ({op: 0})}]})
|
|
1919
|
+
// With no rank, proveLeadsTo finds one from the rule's runs (core/cycles.ts's findLeadsTo).
|
|
1842
1920
|
leadsTo(name, spec) {
|
|
1843
1921
|
const env = this.env({ flags: false, inputs: false, queries: false });
|
|
1844
1922
|
const req = (f) => Object.fromEntries(Object.entries(f(env)).map(([n, t]) => [n, termOf(t)]));
|
|
1845
|
-
const r = spec.rank(env), rank = Array.isArray(r) ? r.map(termOf) : termOf(r);
|
|
1923
|
+
const r = spec.rank ? spec.rank(env) : [], rank = Array.isArray(r) ? r.map(termOf) : termOf(r);
|
|
1846
1924
|
const base = { name, from: spec.from(env).c, to: spec.to(env).c, rank };
|
|
1847
|
-
return typeof spec.helpful === 'function' ? { ...base, request: req(spec.helpful) } : { ...base, helpful: spec.helpful.map(h => ({ ...(h.when ? { when: h.when(env).c } : {}), request: req(h.request) })) };
|
|
1925
|
+
return this.divides(typeof spec.helpful === 'function' ? { ...base, request: req(spec.helpful) } : { ...base, helpful: spec.helpful.map(h => ({ ...(h.when ? { when: h.when(env).c } : {}), request: req(h.request) })) });
|
|
1848
1926
|
}
|
|
1849
1927
|
// A decision: whenever the params and the request meet `given`, `then` holds. Decisions don't read the state; the
|
|
1850
1928
|
// functions' facts are assumed at the arguments the decision reads them at, and a carried list's equations (each
|
|
@@ -1857,8 +1935,8 @@ export class Rule {
|
|
|
1857
1935
|
const t = then(env).c;
|
|
1858
1936
|
const lists = (this.spec.lists2 ?? []).map(([n]) => env[n]);
|
|
1859
1937
|
const listed = lists.flatMap(l => l.equationsAt(uniqueTerms([l.len, l.len.minus(1), ...keysRead([...hyps, t], l.maps)])).map(c => c.c));
|
|
1860
|
-
const goal = { name, hyps: [...listed, ...hyps, { op: 'not', x: t }] };
|
|
1861
|
-
const facts = this.spec.fns?.length || this.spec.facts?.length ? factsFor(this.machine, goal) : [];
|
|
1938
|
+
const goal = this.divides({ name, hyps: [...listed, ...hyps, { op: 'not', x: t }] });
|
|
1939
|
+
const facts = this.spec.fns?.length || this.spec.facts?.length || this.spec.arith?.size ? factsFor(this.machine, goal) : [];
|
|
1862
1940
|
const d = { name, rule: this, given: hyps, then: t, goal: { name, hyps: [...facts, ...goal.hyps] } };
|
|
1863
1941
|
if ([...(Array.isArray(g) ? g : [g]).map(c => c.c), t].some(quantifiedIn)) {
|
|
1864
1942
|
const conds = (e) => { const own = given(e); return { given$: all(...(Array.isArray(own) ? own : [own])), then$: then(e) }; };
|
|
@@ -2053,8 +2131,20 @@ export class Rule {
|
|
|
2053
2131
|
else
|
|
2054
2132
|
state[n] = hoist.term(termOf(u));
|
|
2055
2133
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2134
|
+
// An existential in a precondition or the assumption: a value the params or the request bring (a param or an input
|
|
2135
|
+
// the runtimes find, encodeParams and encodeInput), fresh for each existential and, for a request, each step.
|
|
2136
|
+
const pre0 = pulledOut(s.pre.map(f => f(pEnv)), `${s.name}: a precondition`, 'some$p'), as0 = pulledOut(s.assume.map(f => f(env)), `${s.name}: the assumption`, 'some$a');
|
|
2137
|
+
const known = new Set([...params, ...inputs]);
|
|
2138
|
+
for (const b of as0.bodies) {
|
|
2139
|
+
const out = namesIn(b.body).filter(n => !known.has(n) && !as0.vars.includes(n));
|
|
2140
|
+
if (out.length)
|
|
2141
|
+
throw new Error(`${s.name}: an exists(...) in the assumption reads ${out[0]}: it reads only the params and the request (its value comes with the request)`);
|
|
2142
|
+
}
|
|
2143
|
+
skolemsOf.set(this, { params: pre0.bodies, inputs: as0.bodies });
|
|
2144
|
+
params.push(...pre0.vars);
|
|
2145
|
+
inputs.push(...as0.vars);
|
|
2146
|
+
const pre = [...this.kindAssumptions('params'), ...pre0.cs];
|
|
2147
|
+
const assume = [...this.kindAssumptions('inputs'), ...(s.lists2 ?? []).flatMap(([n]) => env[n].equations().map(c => hoist.cond(c.c))), ...as0.cs.map(c => hoist.cond(c))];
|
|
2058
2148
|
placed();
|
|
2059
2149
|
// Functions, and their facts: each function's own (named after it), then the rule's.
|
|
2060
2150
|
// The functions a function reads come first, each named <function>.<use>.
|
|
@@ -2092,7 +2182,7 @@ export class Rule {
|
|
|
2092
2182
|
...(s.prune ? { prune: { order: pruneOrder[0], after: termOf(s.prune.after(pEnv)) } } : {}),
|
|
2093
2183
|
};
|
|
2094
2184
|
}
|
|
2095
|
-
return m;
|
|
2185
|
+
return withArithmetic(m, [], s.arith ?? []);
|
|
2096
2186
|
}
|
|
2097
2187
|
// --- the builders checked ------------------------------------------------------------------------------------------
|
|
2098
2188
|
// The builders against what they built, on concrete runs: random params and requests by their kinds (`pre` and
|
|
@@ -2267,7 +2357,26 @@ export class Rule {
|
|
|
2267
2357
|
}
|
|
2268
2358
|
// --- running it -----------------------------------------------------------------------------------------------------
|
|
2269
2359
|
// Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
|
|
2270
|
-
encodeParams(values) { return encode(this.spec.params, values); }
|
|
2360
|
+
encodeParams(values) { return this.withSkolems('params', encode(this.spec.params, values)); }
|
|
2361
|
+
// The values the params or a request bring for the existentials of a precondition or the assumption: each a witness
|
|
2362
|
+
// found (exact for one natural or integer read linearly), or 0 where none is (the precondition or the assumption then
|
|
2363
|
+
// fails, so the params or the request are refused).
|
|
2364
|
+
withSkolems(which, out, params) {
|
|
2365
|
+
if (!skolemsOf.has(this))
|
|
2366
|
+
void this.machine;
|
|
2367
|
+
const sk = skolemsOf.get(this)?.[which] ?? [];
|
|
2368
|
+
if (!sk.length)
|
|
2369
|
+
return out;
|
|
2370
|
+
const m = this.machine, env = { ...params, ...numbersOf(out) };
|
|
2371
|
+
for (const b of sk) {
|
|
2372
|
+
const w = someHolds(b.body, b.vars, env, { scalars: {}, maps: {} }, m)?.witness ?? {};
|
|
2373
|
+
for (const n of b.vars) {
|
|
2374
|
+
out[n] = w[n] ?? 0;
|
|
2375
|
+
env[n] = w[n] ?? 0;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
return out;
|
|
2379
|
+
}
|
|
2271
2380
|
// 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
|
|
2272
2381
|
// naturals, a table of them an array of such arrays.
|
|
2273
2382
|
// A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
|
|
@@ -2303,7 +2412,7 @@ export class Rule {
|
|
|
2303
2412
|
const given = values[n];
|
|
2304
2413
|
out[n] = given instanceof Map ? new Map(given) : new Map([...given].map(key => [key, 1]));
|
|
2305
2414
|
}
|
|
2306
|
-
return out;
|
|
2415
|
+
return this.withSkolems('inputs', out, params && this.encodeParams(params));
|
|
2307
2416
|
}
|
|
2308
2417
|
// Each carried list's folds at its end (what `L.fold.<f>` reads), computed from its items as encodeInput computes
|
|
2309
2418
|
// them, without running the rule: `foldsOf({Lines: [...]})` → {Lines: {total: 30}}. Only the lists given.
|
|
@@ -2318,7 +2427,7 @@ export class Rule {
|
|
|
2318
2427
|
// The state's variables declared with a kind (a yes/no, a choice, an integer...), with their kinds.
|
|
2319
2428
|
kindsOfState() { return this.spec.kinded ?? []; }
|
|
2320
2429
|
// Random params and inputs that meet their kinds (not the user's `pre` or `assume`, which a caller checks).
|
|
2321
|
-
randomParams(rand, bound) { return Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound))); }
|
|
2430
|
+
randomParams(rand, bound) { return this.withSkolems('params', Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound)))); }
|
|
2322
2431
|
// The params' values by their kinds, as constants (what a fold that reads them is computed with).
|
|
2323
2432
|
paramValues(ps) {
|
|
2324
2433
|
return Object.fromEntries(this.spec.params.map(([n, k]) => [n, k.build(n, part => new Term({ op: 'const', value: ps[part] }, ps[part]))]));
|
|
@@ -2333,7 +2442,7 @@ export class Rule {
|
|
|
2333
2442
|
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, ps && this.paramValues(ps)).encode(Array.from({ length: rand(bound + 1) }, () => k.sample(rand, bound)));
|
|
2334
2443
|
Object.assign(out, l.maps, { [`${n}_len`]: l.len });
|
|
2335
2444
|
}
|
|
2336
|
-
return out;
|
|
2445
|
+
return this.withSkolems('inputs', out, ps);
|
|
2337
2446
|
}
|
|
2338
2447
|
// The state's scalars as runtime values: each kinded variable as its kind decodes it (an integer as one signed
|
|
2339
2448
|
// number, a decimal as its number, a choice by name, a string as itself, none as null).
|
|
@@ -2376,7 +2485,7 @@ const expandParts = (updates) => {
|
|
|
2376
2485
|
return updates;
|
|
2377
2486
|
};
|
|
2378
2487
|
const encode = (kinds, values) => Object.assign({}, ...kinds.map(([n, k]) => k.encode(n, values[n])));
|
|
2379
|
-
export const rule = (name) => new Rule({ name, params: [], inputs: [], state: [], maps: [], flags: [], pre: [], assume: [], sums: [], queries: [] });
|
|
2488
|
+
export const rule = (name) => new Rule({ name, params: [], inputs: [], state: [], maps: [], flags: [], pre: [], assume: [], sums: [], queries: [], arith: new Set() });
|
|
2380
2489
|
const quantifiedE = (e) => e.op === 'if' ? quantifiedIn(e.c) || quantifiedE(e.then) || quantifiedE(e.else) : 'a' in e && typeof e.a === 'object' ? quantifiedE(e.a) || ('b' in e && typeof e.b === 'object' && quantifiedE(e.b)) : false;
|
|
2381
2490
|
const quantifiedIn = (c) => c.op === 'all' || (c.op === 'not' ? quantifiedIn(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.some(quantifiedIn) : c.op === 'flag' ? false : quantifiedE(c.a) || quantifiedE(c.b));
|
|
2382
2491
|
// The kind a result field is kept in.
|
|
@@ -2537,7 +2646,8 @@ const refuteBudget = { trials: 60, depth: 6, bound: 3, ms: 150 };
|
|
|
2537
2646
|
const deeperBudget = { trials: 600, depth: 24, bound: 8, ms: 1500, seed: 29 };
|
|
2538
2647
|
function refuteByRuns(r, laws, stepLaws = [], budget = refuteBudget) {
|
|
2539
2648
|
const m = r.machine;
|
|
2540
|
-
|
|
2649
|
+
// Division by a constant is a function too, but its facts fix its value, so a run computing it is a run of the rule.
|
|
2650
|
+
if (Object.keys(m.fns ?? {}).some(f => !isArithmetic(f)) || m.facts?.some(f => !isArithmetic(f.name.slice(0, f.name.indexOf(':')))))
|
|
2541
2651
|
return undefined;
|
|
2542
2652
|
const run = interpret(m), rand = seeded(budget.seed ?? 13), until = performance.now() + budget.ms, bound = budget.bound;
|
|
2543
2653
|
const shown = (input) => Object.fromEntries(Object.entries(input).map(([n, x]) => [n, typeof x === 'number' ? x : Object.fromEntries(x)]));
|
|
@@ -2602,15 +2712,21 @@ function refuteByRuns(r, laws, stepLaws = [], budget = refuteBudget) {
|
|
|
2602
2712
|
return undefined;
|
|
2603
2713
|
}
|
|
2604
2714
|
const refutedVerdict = (out) => ({ proven: false, checked: false, goal: out.goal, stuck: 'sat', witness: out.witness, ...(out.run ? { run: out.run } : {}) });
|
|
2715
|
+
// The divisors some laws read (modBy, divBy), noted on the rule so its machine declares them: a law may come from
|
|
2716
|
+
// another instance of the same rule (a factory called twice).
|
|
2717
|
+
const noting = (r, ...xs) => { const ks = divisorsIn(xs); if (ks.size)
|
|
2718
|
+
r.dividesBy(...ks); return r; };
|
|
2605
2719
|
export function proveRule(r, laws, opts = {}) {
|
|
2720
|
+
noting(r, laws);
|
|
2606
2721
|
const refuted = refuteByRuns(r, laws);
|
|
2607
2722
|
if (refuted)
|
|
2608
2723
|
return refutedVerdict(refuted);
|
|
2609
2724
|
const out = proveLaws(r.machine, withKinds(r, laws));
|
|
2610
|
-
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 } : {}) } : {}) };
|
|
2725
|
+
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 } : {}) } : {}), ...(out.witnesses && out.proven ? { witnesses: out.witnesses } : {}) };
|
|
2611
2726
|
return searched(r, laws, [], v, opts);
|
|
2612
2727
|
}
|
|
2613
2728
|
export function proveSteps(r, laws, stepLaws, opts = {}) {
|
|
2729
|
+
noting(r, laws, stepLaws);
|
|
2614
2730
|
const refuted = refuteByRuns(r, laws, stepLaws);
|
|
2615
2731
|
if (refuted)
|
|
2616
2732
|
return refutedVerdict(refuted);
|
|
@@ -2654,7 +2770,15 @@ function searched(r, laws, stepLaws, v, opts) {
|
|
|
2654
2770
|
void _stats;
|
|
2655
2771
|
return { ...verdict, found: { laws: found, ...(lemmas ? { lemmas } : {}) } };
|
|
2656
2772
|
}
|
|
2657
|
-
export function proveLeadsTo(r, laws, l) {
|
|
2773
|
+
export function proveLeadsTo(r, laws, l, opts = {}) {
|
|
2774
|
+
noting(r, laws, l);
|
|
2775
|
+
if (laws.some(law => law.some?.length))
|
|
2776
|
+
throw new Error(`${r.machine.name}: a law with "there exists" goes to proveRule or proveSteps; a liveness takes plain laws (the ones its proof printed, Verdict.witnesses)`);
|
|
2777
|
+
if (Array.isArray(l.rank) && !l.rank.length) {
|
|
2778
|
+
const f = findLeadsTo(r.machine, withKinds(r, laws), l, { params: (rand, bound) => r.randomParams(rand, bound), input: (rand, bound, ps) => r.randomInput(rand, bound, ps) }, opts);
|
|
2779
|
+
return { proven: f.proven, checked: f.checked, ...(f.goal ? { goal: f.goal, stuck: f.stuck } : {}), ...(f.witness ? { witness: f.witness } : {}), ...(f.why ? { why: f.why } : {}),
|
|
2780
|
+
...(f.found ? { found: { rank: f.found.rank, ...(f.laws?.length ? { laws: f.laws.map((law, i) => ({ law, text: f.found.laws[i] ?? law.name })) } : {}) } } : {}) };
|
|
2781
|
+
}
|
|
2658
2782
|
const out = proveLiveness(r.machine, withKinds(r, laws), l);
|
|
2659
2783
|
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 } : {}) } : {}) };
|
|
2660
2784
|
}
|
|
@@ -2691,7 +2815,7 @@ export function crossCheckDecision(d, opts = {}) {
|
|
|
2691
2815
|
// needs (inferLaws), within `ms` (10 s); the verdict is the kernel's on the rank and the laws found, in `found`.
|
|
2692
2816
|
export const proveLoop = (l, laws, opts = {}) => {
|
|
2693
2817
|
if (cyclicOn() || certOn())
|
|
2694
|
-
|
|
2818
|
+
uncovered('loop', l.loop.name, 'a claim about runs that never end');
|
|
2695
2819
|
if (l.hasRank)
|
|
2696
2820
|
return goalsVerdict(loopObligations(l.loop, laws, l.rankTerms));
|
|
2697
2821
|
const out = findRank(l.loop, laws, opts);
|
|
@@ -2813,12 +2937,18 @@ export class FoldRule {
|
|
|
2813
2937
|
export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
|
|
2814
2938
|
// A fold's laws proven over every tree (a rose fold's own laws, its children's lemmas, with them).
|
|
2815
2939
|
export const proveFold = (f, laws) => {
|
|
2816
|
-
// (
|
|
2940
|
+
// (by default the certificate a cyclic proof, core/cyclic.ts's certCyclic, the usual trees its input; where none is
|
|
2941
|
+
// made, the usual trees checked)
|
|
2817
2942
|
const all = [...(f.lemmas ?? []), ...laws], t0 = performance.now(), v = goalsVerdict(foldObligations(f.fold, all), !certOn()), usualMs = performance.now() - t0;
|
|
2818
2943
|
if (cyclicOn())
|
|
2819
2944
|
crossCyclic('fold', f.fold.name, v.proven && v.checked, () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs, v.trees && (st => translatedProof(() => JSON.stringify(['fold', f.fold, all]), () => foldNodes(f.fold, all, v.trees, st), st)));
|
|
2820
|
-
if (certOn() && v.proven)
|
|
2821
|
-
|
|
2945
|
+
if (certOn() && v.proven) {
|
|
2946
|
+
const c = certCyclic('fold', f.fold.name, v.trees && (st => translatedProof(() => JSON.stringify(['fold', f.fold, all]), () => foldNodes(f.fold, all, v.trees, st), st)), () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs);
|
|
2947
|
+
if (!c.checked) {
|
|
2948
|
+
uncovered('fold', f.fold.name, c.why);
|
|
2949
|
+
v.checked = foldObligations(f.fold, all).every(g => v.trees?.[g.name] !== undefined && check(g, v.trees[g.name]));
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2822
2952
|
return v;
|
|
2823
2953
|
};
|
|
2824
2954
|
const asTerm = (t) => (t instanceof Term ? t : lit(t));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type C, type E, type Law, type Machine, type SomeWitness, type State } from './machine.js';
|
|
2
|
+
import { type Report } from '../kernel/prove.js';
|
|
3
|
+
type Env = Record<string, number>;
|
|
4
|
+
type Group = string[];
|
|
5
|
+
export declare const namesOf: (t: E | C) => string[];
|
|
6
|
+
export declare function groupsOf(vars: string[]): Group[];
|
|
7
|
+
export declare function substVarsE(e: E, s: Record<string, E>): E;
|
|
8
|
+
export declare function substVars(c: C, s: Record<string, E>): C;
|
|
9
|
+
export declare function someHolds(body: C, vars: string[], env: Env, st: State, m: Machine): {
|
|
10
|
+
holds: boolean;
|
|
11
|
+
witness?: Env;
|
|
12
|
+
} | undefined;
|
|
13
|
+
export type SomeReport = Report & {
|
|
14
|
+
laws?: Law[];
|
|
15
|
+
machine?: Machine;
|
|
16
|
+
ghosts?: string[];
|
|
17
|
+
witnesses?: Record<string, string>;
|
|
18
|
+
proof?: unknown;
|
|
19
|
+
};
|
|
20
|
+
export declare function proveSome(m: Machine, laws: Law[], opts?: {
|
|
21
|
+
rounds?: number;
|
|
22
|
+
}): SomeReport;
|
|
23
|
+
export declare function showE(e: E): string;
|
|
24
|
+
export declare function showC(c: C): string;
|
|
25
|
+
export declare const someLaws: (laws: Law[]) => boolean;
|
|
26
|
+
export type { SomeWitness };
|