gdp-wired 0.12.3 → 0.12.4
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 -6
- package/dist/core/dsl.d.ts +4 -0
- package/dist/core/dsl.js +55 -11
- package/dist/core/machine.js +28 -10
- package/dist/kernel/kernel.js +4 -2
- package/dist/kernel/prove.js +27 -4
- package/dist/lean-kernel/library.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,8 +53,8 @@ All of these are proven automatically, and each proof is also a Lean theorem:
|
|
|
53
53
|
- **Maps, sets and lists of names**: laws at every key ("no day passes its cap"), sets a request carries, conditions
|
|
54
54
|
about every key or every two keys ("a batch is taken only when its values increase"), lists kept in order
|
|
55
55
|
(`examples/sets.ts`, `examples/lists.ts`, `examples/pairs.ts`).
|
|
56
|
-
- **Step laws**: what one step does ("a balance only goes down", "a grant adds one"), and laws over
|
|
57
|
-
row.
|
|
56
|
+
- **Step laws**: what one step does ("a balance only goes down", "a grant adds one"), and laws over up to 64 steps in
|
|
57
|
+
a row (`examples/steps.ts`).
|
|
58
58
|
- **Histories**: sums and queries over a log of requests, like a sliding-window rate limit.
|
|
59
59
|
- **Orderings at any distance**: "a refund never follows a release", through ghost state that watches the rule.
|
|
60
60
|
- **Liveness**: "every ticket issued is served", in every run that keeps sending the requests that help, including under
|
|
@@ -230,8 +230,10 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
|
|
|
230
230
|
carried map of its running values whose equations every request is assumed to meet (the encoding computes them from the
|
|
231
231
|
array, and a request that doesn't meet them isn't allowed), read as `Lines.fold.total`; `Lines.every`, `some`, `has`,
|
|
232
232
|
`increasing(by)`, `at(i)`. A lemma about the folds (`({taxed, total}) => taxed.atMost(total)`) is proven by induction
|
|
233
|
-
over the items when the rule is built, then assumed at every prefix
|
|
234
|
-
|
|
233
|
+
over the items when the rule is built, then assumed at every prefix; a decision (`decide`) assumes the equations and
|
|
234
|
+
lemmas too, at the list's end and wherever it reads the list. A lemma reads the folds, the prefix's length and the
|
|
235
|
+
params, not the items: a fact about every item is a fold's (a count of the items that meet it). A fold's start and
|
|
236
|
+
step, and a lemma, may read the params (`{from: ({fee}) => fee, step: (acc, line, {ceil}) => acc.plus(min(line.amount, ceil))}`); a request is then
|
|
235
237
|
encoded with them, `rule.encodeInput(values, params)` (`examples/invoices.ts`).
|
|
236
238
|
- **Folds over trees** (`fold(name).params(...).inputs(...).leaf(env => ({...})).node((env, left, right) => ({...}))`,
|
|
237
239
|
`proveFold`): a tree as a value (`{leaf: inputs}` or `{node: inputs, left, right}`), each leaf's values from its inputs
|
|
@@ -336,6 +338,10 @@ or two lines and check in 25–44 ms, against 74–117 lines of hand proof and 1
|
|
|
336
338
|
The proved Bend and Lean rules keep lists and scan them; gdp-wired runs maps and a log (unpruned in this window rule).
|
|
337
339
|
Lean, compiled natively, wins where the state stays small. Details in [`docs/comparison.md`](docs/comparison.md).
|
|
338
340
|
|
|
341
|
+
Proving and checking: a law over 64 steps proves in about 5 s, and Lean checks its theorem in 47 s (6 s by
|
|
342
|
+
`native_decide`); a law over 32 steps proves in about a second. What changed since the first release, and by how much:
|
|
343
|
+
[`CHANGELOG.md`](CHANGELOG.md).
|
|
344
|
+
|
|
339
345
|
## Layout
|
|
340
346
|
|
|
341
347
|
| Path | What |
|
|
@@ -343,7 +349,7 @@ Lean, compiled natively, wins where the state stays small. Details in [`docs/com
|
|
|
343
349
|
| `core/` | Machines, the interpreter and JS compiler, the builders, composition (`compose.ts`), the linear solver and its checker, Houdini invariants, models, the first Lean export |
|
|
344
350
|
| `kernel/` | The trusted kernel and the untrusted prover, with soundness fuzz (`kernel.test.ts`, `history.test.ts`) |
|
|
345
351
|
| `lean-kernel/` | The kernel's Lean twin, its soundness proofs, and the exported trees |
|
|
346
|
-
| `examples/` | The builders at work: `domains/` (187 rules across 21 domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `gdp.ts` (gdp-ts's example, its checks proven), `datatypes.ts` (records and variants), `lemmas.ts` (recurrences and their lemmas), `invoices.ts` (lists a request carries), `folds.ts` (folds over trees), `trees.ts` (trees and lists of lists in the state), `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, `steps.ts` (laws over up to
|
|
352
|
+
| `examples/` | The builders at work: `domains/` (187 rules across 21 domains, each with laws and broken variants), `harness.ts` (the triple check), `readme.ts`, `budget.ts`, `gdp.ts` (gdp-ts's example, its checks proven), `datatypes.ts` (records and variants), `lemmas.ts` (recurrences and their lemmas), `invoices.ts` (lists a request carries), `folds.ts` (folds over trees), `trees.ts` (trees and lists of lists in the state), `composition.ts`, `sets.ts`, `quantifiers.ts`, `functions.ts`, `lists.ts`, `sizes.ts`, `liveness.ts`, `steps.ts` (laws over up to 64 steps), and `jev-fabric/` (a Bend project's 59 proven laws, ported) |
|
|
347
353
|
| `docs/` | The comparison with Bend and Lean, and the full scope |
|
|
348
354
|
| `compare/` | The same three rules in gdp-wired, Bend and plain Lean, checked and measured |
|
|
349
355
|
| `research/` | Everything around the library ([`research/README.md`](research/README.md)): the GPU searches (`search/`, `hw/`, `modal-gpu/`), invariant and rank synthesis (`synth/`), relational laws and guard synthesis (`next/`), bit-vector laws (`sat/`), the enumeration and benchmarks (`enum/`, `gpu/`, `perf/`), the first Lean checker (`lean/`) and the first prototype, the audit path (`audit/`) |
|
|
@@ -371,7 +377,7 @@ bun test core/compile.test.ts core/runtime.test.ts kernel/history.test.ts
|
|
|
371
377
|
cd kernel && bun run-kernel.ts && bun test kernel.test.ts
|
|
372
378
|
```
|
|
373
379
|
|
|
374
|
-
The whole suite (
|
|
380
|
+
The whole suite (159 jobs, about 6 minutes of wall time and 22 minutes of job time) runs on [Modal](https://modal.com):
|
|
375
381
|
|
|
376
382
|
```sh
|
|
377
383
|
modal run modal/run.py --jobs modal/suite.json --out runs/modal/suite && sh modal/reap.sh
|
package/dist/core/dsl.d.ts
CHANGED
|
@@ -333,6 +333,10 @@ export declare class CarriedList<V, Fd extends string = never> {
|
|
|
333
333
|
has(x: V): Cond;
|
|
334
334
|
increasing(by: (item: V) => Term): Cond;
|
|
335
335
|
equations(): Cond[];
|
|
336
|
+
equationsAt(keys: Term[]): Cond[];
|
|
337
|
+
private starts;
|
|
338
|
+
private get keyed();
|
|
339
|
+
private atKey;
|
|
336
340
|
private get readsParams();
|
|
337
341
|
private p;
|
|
338
342
|
private running;
|
package/dist/core/dsl.js
CHANGED
|
@@ -875,6 +875,43 @@ export class CellsRef {
|
|
|
875
875
|
}
|
|
876
876
|
}
|
|
877
877
|
const fromOf = (d, p) => (typeof d.from === 'number' ? d.from : d.from(p));
|
|
878
|
+
// The keys a condition reads any of these maps at, as terms.
|
|
879
|
+
function keysRead(cs, maps) {
|
|
880
|
+
const out = [];
|
|
881
|
+
const e = (t) => {
|
|
882
|
+
if (t.op === 'get') {
|
|
883
|
+
if (maps.includes(t.map))
|
|
884
|
+
out.push(new Term(t.key));
|
|
885
|
+
e(t.key);
|
|
886
|
+
}
|
|
887
|
+
else if (t.op === 'if') {
|
|
888
|
+
c(t.c);
|
|
889
|
+
e(t.then);
|
|
890
|
+
e(t.else);
|
|
891
|
+
}
|
|
892
|
+
else if (t.op === 'mul')
|
|
893
|
+
e(t.a);
|
|
894
|
+
else if (t.op === 'add' || t.op === 'sub' || t.op === 'min' || t.op === 'max' || t.op === 'prod') {
|
|
895
|
+
e(t.a);
|
|
896
|
+
e(t.b);
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
const c = (x) => {
|
|
900
|
+
if (x.op === 'not')
|
|
901
|
+
c(x.x);
|
|
902
|
+
else if (x.op === 'and' || x.op === 'or')
|
|
903
|
+
x.xs.forEach(c);
|
|
904
|
+
else if (x.op === 'all')
|
|
905
|
+
c(x.c);
|
|
906
|
+
else if (x.op !== 'flag') {
|
|
907
|
+
e(x.a);
|
|
908
|
+
e(x.b);
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
cs.forEach(c);
|
|
912
|
+
return out;
|
|
913
|
+
}
|
|
914
|
+
const uniqueTerms = (ts) => [...new Map(ts.map(t => [JSON.stringify(t.e), t])).values()];
|
|
878
915
|
export class CarriedList {
|
|
879
916
|
name;
|
|
880
917
|
kind;
|
|
@@ -908,15 +945,16 @@ export class CarriedList {
|
|
|
908
945
|
increasing(by) { return everyKey(k => k.plus(1).lt(this.len).implies(by(this.at(k)).lt(by(this.at(k.plus(1)))))); }
|
|
909
946
|
// What every request is assumed to meet: each fold's start, and at every key one condition (the item's own laws and
|
|
910
947
|
// each fold's step below the length, each lemma up to it), so the prover closes one quantifier, not one per part.
|
|
911
|
-
equations() {
|
|
948
|
+
equations() { return [...this.starts(), ...(this.keyed ? [everyKey(k => this.atKey(k))] : [])]; }
|
|
949
|
+
// The same at the keys given, without the quantifier: what a decision assumes of the list (Rule.decide).
|
|
950
|
+
equationsAt(keys) { return [...this.starts(), ...(this.keyed ? keys.map(k => this.atKey(k)) : [])]; }
|
|
951
|
+
starts() { return Object.entries(this.folds).map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(fromOf(d, this.p()))); }
|
|
952
|
+
get keyed() { return this.kind.invariant(this.kind.value(this.name)).length + Object.keys(this.folds).length + Object.keys(this.lemmas).length > 0; }
|
|
953
|
+
atKey(k) {
|
|
912
954
|
const folds = Object.entries(this.folds), lemmas = Object.values(this.lemmas);
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
return all(...(below.length ? [k.lt(this.len).implies(all(...below))] : []), ...(lemmas.length ? [k.atMost(this.len).implies(all(...lemmas.map(l => l(this.running(k), k, this.p()))))] : []));
|
|
917
|
-
};
|
|
918
|
-
const any = this.kind.invariant(this.kind.value(this.name)).length + folds.length + lemmas.length > 0;
|
|
919
|
-
return [...folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(0).eq(fromOf(d, this.p()))), ...(any ? [everyKey(at)] : [])];
|
|
955
|
+
const x = this.at(k), steps = folds.map(([f, d]) => this.map(`${this.name}$${f}`).at(k.plus(1)).eq(d.step(this.map(`${this.name}$${f}`).at(k), x, this.p())));
|
|
956
|
+
const below = [...this.kind.invariant(x), ...steps];
|
|
957
|
+
return all(...(below.length ? [k.lt(this.len).implies(all(...below))] : []), ...(lemmas.length ? [k.atMost(this.len).implies(all(...lemmas.map(l => l(this.running(k), k, this.p()))))] : []));
|
|
920
958
|
}
|
|
921
959
|
// Whether a fold reads the params (its start a function, or its step taking them).
|
|
922
960
|
get readsParams() { return Object.values(this.folds).some(d => typeof d.from === 'function' || d.step.length > 2); }
|
|
@@ -1704,11 +1742,15 @@ export class Rule {
|
|
|
1704
1742
|
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) })) };
|
|
1705
1743
|
}
|
|
1706
1744
|
// A decision: whenever the params and the request meet `given`, `then` holds. Decisions don't read the state; the
|
|
1707
|
-
// functions' facts are assumed at the arguments the decision reads them at
|
|
1745
|
+
// functions' facts are assumed at the arguments the decision reads them at, and a carried list's equations (each
|
|
1746
|
+
// fold's start, its step and its lemmas) at the list's end, the key before it and every key the decision reads it at.
|
|
1708
1747
|
decide(name, given, then) {
|
|
1709
1748
|
const env = this.env({ flags: false, state: false, queries: false });
|
|
1710
1749
|
const g = given(env), hyps = [...this.kindAssumptions(), ...this.spec.pre.map(f => f(env).c), ...this.spec.assume.map(f => f(env).c), ...(Array.isArray(g) ? g : [g]).map(c => c.c)];
|
|
1711
|
-
const t = then(env).c
|
|
1750
|
+
const t = then(env).c;
|
|
1751
|
+
const lists = (this.spec.lists2 ?? []).map(([n]) => env[n]);
|
|
1752
|
+
const listed = lists.flatMap(l => l.equationsAt(uniqueTerms([l.len, l.len.minus(1), ...keysRead([...hyps, t], l.maps)])).map(c => c.c));
|
|
1753
|
+
const goal = { name, hyps: [...listed, ...hyps, { op: 'not', x: t }] };
|
|
1712
1754
|
const facts = this.spec.fns?.length || this.spec.facts?.length ? factsFor(this.machine, goal) : [];
|
|
1713
1755
|
return withSource({ name, rule: this, given: hyps, then: t, goal: { name, hyps: [...facts, ...goal.hyps] } }, { decision: { given: given, then: then, own: Array.isArray(g) ? g.length : 1 } });
|
|
1714
1756
|
}
|
|
@@ -2349,7 +2391,9 @@ export function crossCheckDecision(d, opts = {}) {
|
|
|
2349
2391
|
let checked = 0;
|
|
2350
2392
|
for (let i = 0; i < (opts.trials ?? 500); i++) {
|
|
2351
2393
|
const ps = r.randomParams(rand, bound), input = r.randomInput(rand, bound, ps), values = { ...ps, ...numbersOf(input) };
|
|
2352
|
-
|
|
2394
|
+
// a map a request carries reads 0 past its cells (a list's encoding keeps no 0 cell)
|
|
2395
|
+
const carried = Object.entries(input).filter((e) => typeof e[1] !== 'number');
|
|
2396
|
+
const st = { scalars: {}, maps: Object.fromEntries(carried.map(([n, cells]) => [n, new Map(cells)])), defaults: Object.fromEntries(carried.map(([n]) => [n, 0])) };
|
|
2353
2397
|
const keys = exactKeys(keysOf(bound, st), values, st);
|
|
2354
2398
|
const sc = { vars: values, maps: Object.fromEntries(Object.entries(st.maps).map(([n, cells]) => [n, { cells, dflt: 0 }])), keys, flags: {} };
|
|
2355
2399
|
const [g, t] = inScope(sc, () => { const env = r['env']({ flags: false, state: false, queries: false }), g = src.given(env); return [all(...(Array.isArray(g) ? g : [g])).ok, src.then(env).ok]; });
|
package/dist/core/machine.js
CHANGED
|
@@ -364,6 +364,30 @@ export function interpret(m) {
|
|
|
364
364
|
// The same machine as JavaScript source: what an app ships. A step reads the state it was given (every query, flag,
|
|
365
365
|
// scalar, key and value first), then writes the maps and the records in place, so it costs the same however large they
|
|
366
366
|
// grow; the state passed in is consumed. fork copies a state to branch from.
|
|
367
|
+
// The runtime's helpers: a compiled module declares the ones it calls (and the ones those call), no others.
|
|
368
|
+
const RUNTIME = [
|
|
369
|
+
['$n', `function $n(v, what) { if (typeof v !== 'number' || !Number.isSafeInteger(v) || v < 0) throw new RangeError(what + ' must be a natural below 2^53, not ' + v); return v; }`],
|
|
370
|
+
['$x', `function $x(v) { if (v > 9007199254740991) throw new RangeError('a value passed 2^53 - 1, past which the proofs over the naturals no longer describe the run'); return v; }`],
|
|
371
|
+
['$m', `function $m(v, what) { if (!(v instanceof Map)) throw new RangeError(what + ' must be a Map of naturals'); for (const [key, x] of v) { $n(key, 'a key of ' + what); $n(x, 'a cell of ' + what); } return v; }`],
|
|
372
|
+
['$keys', `function $keys(maps, names) { const keys = new Set(); let past = 0; for (const c of Object.values(maps)) for (const key of c.keys()) past = Math.max(past, key + 1); for (const n of names) for (const key of maps[n].keys()) keys.add(key); return [...keys, past]; }`],
|
|
373
|
+
['$breaks', `function $breaks(maps, reads, cmps) { const p = new Set([0]); for (const [n, d, minus] of reads) { for (const s of maps[n].keys()) { if (minus) p.add(s + d); else if (s >= d) p.add(s - d); } if (minus) p.add(d); } for (const [v, d, minus] of cmps) { if (minus) { p.add(v + d); p.add(d); } else if (v >= d) p.add(v - d); } const keys = new Set(); for (const q of p) for (let i = -1; i <= 2; i++) if (q + i >= 0) keys.add(q + i); const sorted = [...keys].sort((a, b) => a - b); sorted.push(sorted[sorted.length - 1] + 1); return sorted; }`],
|
|
374
|
+
['$spread', `function $spread(keys, n) { const out = new Set(); for (const k of keys) for (let i = 0; i < n; i++) out.add(k + i); return [...out].sort((a, b) => a - b); }`],
|
|
375
|
+
['$dense', `function $dense(keys, f) { const top = keys[keys.length - 1]; if (top > 1000000) throw new RangeError('a write at every key reads its key in arithmetic past a million keys (' + top + ')'); const d = f(top), cells = new Map(); for (let k = 0; k < top; k++) { const v = f(k); if (v !== d) cells.set(k, v); } return {cells, d}; }`],
|
|
376
|
+
['$every', `function $every(keys, f) { const past = keys[keys.length - 1], d = f(past), cells = new Map(); for (let i = 0; i < keys.length - 1; i++) { const v = f(keys[i]); if (v !== d) cells.set(keys[i], v); } return {cells, d}; }`],
|
|
377
|
+
];
|
|
378
|
+
function runtimeFor(code) {
|
|
379
|
+
const used = new Set();
|
|
380
|
+
for (let grew = true; grew;) {
|
|
381
|
+
grew = false;
|
|
382
|
+
const text = code + RUNTIME.filter(([name]) => used.has(name)).map(([, src]) => src).join('');
|
|
383
|
+
for (const [name] of RUNTIME)
|
|
384
|
+
if (!used.has(name) && text.includes(`${name}(`)) {
|
|
385
|
+
used.add(name);
|
|
386
|
+
grew = true;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return RUNTIME.filter(([name]) => used.has(name)).map(([, src]) => `${src}\n`).join('');
|
|
390
|
+
}
|
|
367
391
|
export function compileJs(m) {
|
|
368
392
|
const { h, plain, queries, carried } = runnable(m);
|
|
369
393
|
const id = (name) => JSON.stringify(name);
|
|
@@ -451,16 +475,8 @@ export function compileJs(m) {
|
|
|
451
475
|
'if (head > 1024 && 2 * head > records.length) { records.splice(0, head); head = 0; }', `return {scalars, maps: state.maps, history: records, head${h.prune ? ', last: order' : ''}};`);
|
|
452
476
|
else
|
|
453
477
|
lines.push('return Object.keys(dft).length ? {scalars, maps: state.maps, defaults: dft} : {scalars, maps: state.maps};');
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
function $x(v) { if (v > 9007199254740991) throw new RangeError('a value passed 2^53 - 1, past which the proofs over the naturals no longer describe the run'); return v; }
|
|
457
|
-
function $m(v, what) { if (!(v instanceof Map)) throw new RangeError(what + ' must be a Map of naturals'); for (const [key, x] of v) { $n(key, 'a key of ' + what); $n(x, 'a cell of ' + what); } return v; }
|
|
458
|
-
function $keys(maps, names) { const keys = new Set(); let past = 0; for (const c of Object.values(maps)) for (const key of c.keys()) past = Math.max(past, key + 1); for (const n of names) for (const key of maps[n].keys()) keys.add(key); return [...keys, past]; }
|
|
459
|
-
${fns.map((name, i) => `const fn${i} = (${m.fns[name].js});\n${m.fns[name].call ? `const fnc${i} = (${m.fns[name].call});\n` : ''}`).join('')}function $breaks(maps, reads, cmps) { const p = new Set([0]); for (const [n, d, minus] of reads) { for (const s of maps[n].keys()) { if (minus) p.add(s + d); else if (s >= d) p.add(s - d); } if (minus) p.add(d); } for (const [v, d, minus] of cmps) { if (minus) { p.add(v + d); p.add(d); } else if (v >= d) p.add(v - d); } const keys = new Set(); for (const q of p) for (let i = -1; i <= 2; i++) if (q + i >= 0) keys.add(q + i); const sorted = [...keys].sort((a, b) => a - b); sorted.push(sorted[sorted.length - 1] + 1); return sorted; }
|
|
460
|
-
function $spread(keys, n) { const out = new Set(); for (const k of keys) for (let i = 0; i < n; i++) out.add(k + i); return [...out].sort((a, b) => a - b); }
|
|
461
|
-
function $dense(keys, f) { const top = keys[keys.length - 1]; if (top > 1000000) throw new RangeError('a write at every key reads its key in arithmetic past a million keys (' + top + ')'); const d = f(top), cells = new Map(); for (let k = 0; k < top; k++) { const v = f(k); if (v !== d) cells.set(k, v); } return {cells, d}; }
|
|
462
|
-
function $every(keys, f) { const past = keys[keys.length - 1], d = f(past), cells = new Map(); for (let i = 0; i < keys.length - 1; i++) { const v = f(keys[i]); if (v !== d) cells.set(keys[i], v); } return {cells, d}; }
|
|
463
|
-
export function init(params) {
|
|
478
|
+
const fnDefs = fns.map((name, i) => `const fn${i} = (${m.fns[name].js});\n${m.fns[name].call ? `const fnc${i} = (${m.fns[name].call});\n` : ''}`).join('');
|
|
479
|
+
const code = `export function init(params) {
|
|
464
480
|
const env = ${env(false)}, s = {};
|
|
465
481
|
return {scalars: {${Object.entries(plain.state).map(([name, e]) => `${id(name)}: ${ex(e)}`).join(', ')}}, maps: {${Object.keys(plain.maps ?? {}).map(name => `${id(name)}: new Map()`).join(', ')}}${h ? ', history: [], head: 0' : ''}};
|
|
466
482
|
}
|
|
@@ -471,6 +487,8 @@ export function step(params, state, input) {
|
|
|
471
487
|
${lines.join('\n ')}
|
|
472
488
|
}
|
|
473
489
|
`;
|
|
490
|
+
return `// Generated from the machine ${m.name}. step consumes its state: keep the one it returns (fork to branch).
|
|
491
|
+
${runtimeFor(fnDefs + code)}${fnDefs}${code}`;
|
|
474
492
|
}
|
|
475
493
|
// Generated code as a function. Every caller passes source this library generated from its own terms, or a function's
|
|
476
494
|
// `js` the rule's author wrote (code, as the rule itself is), never data; the lint's implied-eval rule is waived here
|
package/dist/kernel/kernel.js
CHANGED
|
@@ -1163,12 +1163,14 @@ export function stepLawObligations(machine, laws, instances, sl) {
|
|
|
1163
1163
|
// `perStep`: a step law's own instances, where it has them (else `instances`).
|
|
1164
1164
|
// Each frame's hypotheses read no name a later step's request is read by: the condition the Lean twin's proof needs
|
|
1165
1165
|
// (Sets.lean's checkSKStep, `avoids (szone s sl)`; plain flags are inlined here, so their names never occur), checked
|
|
1166
|
-
// here alike.
|
|
1166
|
+
// here alike. A quantified flag's keys are free of it: where the flag is 1 its condition holds at every key, so at a
|
|
1167
|
+
// key a later step names too (and the flag, its witness and its condition read none).
|
|
1168
|
+
const lawsOnly = (inst) => Object.fromEntries(Object.entries(inst).filter(([n]) => !n.startsWith('?')));
|
|
1167
1169
|
export function checkStepLaws(m, laws, stepLaws, proof) {
|
|
1168
1170
|
return checkLaws(m, laws, proof.laws) && stepLaws.every(sl => {
|
|
1169
1171
|
const inst = proof.perStep?.[sl.name] ?? proof.instances, plain = m.history ? historyMachine(m).machine : m;
|
|
1170
1172
|
const zone = stepZone(plain, sl.steps ?? 1);
|
|
1171
|
-
return [...assumed(laws, inst), ...assumed(plain.facts ?? [], inst), ...closing(plain, inst)].every(c => !mentions(c, zone))
|
|
1173
|
+
return [...assumed(laws, inst), ...assumed(plain.facts ?? [], inst), ...closing(plain, lawsOnly(inst))].every(c => !mentions(c, zone))
|
|
1172
1174
|
&& stepLawObligations(m, laws, inst, sl).every(g => proof.trees[g.name] !== undefined && check(g, proof.trees[g.name]));
|
|
1173
1175
|
});
|
|
1174
1176
|
}
|
package/dist/kernel/prove.js
CHANGED
|
@@ -665,6 +665,17 @@ const flagInstances = (m, keys, focus = []) => Object.fromEntries(Object.entries
|
|
|
665
665
|
const n = boundOf(c).length;
|
|
666
666
|
return [[`?${f}`, n === 1 ? keys : focus.length ? related(keys, focus, n).flat() : tuples(keys, n)]];
|
|
667
667
|
}));
|
|
668
|
+
// A k-step law whose steps keep a condition (a flag that holds at one step and is read again at the next) fails at a
|
|
669
|
+
// later step's witness only where the condition held before: so, per quantified flag over one key, the instances at the
|
|
670
|
+
// law's own keys and that flag's witness at every step (a later step's witness is a key an earlier frame may name, as a
|
|
671
|
+
// flag holds at every key: kernel.ts's checkStepLaws). Few instances, where the shared ones are many and mostly idle.
|
|
672
|
+
const keptAcross = (m, laws, inst, own, k) => k < 2 ? [] : Object.entries(m.step.flags ?? {}).flatMap(([f, c]) => {
|
|
673
|
+
if (c.op !== 'all' || c.keys?.length)
|
|
674
|
+
return [];
|
|
675
|
+
const ws = Array.from({ length: k }, (_, i) => ({ op: 'var', name: i ? `w?${f}@${i + 1}` : `w?${f}` })), keys = [...own, ...ws.slice(0, 1)];
|
|
676
|
+
const keep = new Set(keys.map(e => JSON.stringify(e)));
|
|
677
|
+
return [Object.fromEntries(Object.entries(inst).map(([n, ks]) => [n, arity(m, laws, n) === 1 ? [...ks.filter(e => keep.has(JSON.stringify(e))), ...(n === `?${f}` ? ws.slice(1) : [])] : ks]))];
|
|
678
|
+
});
|
|
668
679
|
const pairFlags = (m) => Object.values(m.step.flags ?? {}).some(c => c.op === 'all' && !!c.keys?.length);
|
|
669
680
|
// How many keys each instance of a name takes: a law's, or a quantified flag's (?f).
|
|
670
681
|
const arity = (m, laws, n) => {
|
|
@@ -677,7 +688,8 @@ const arity = (m, laws, n) => {
|
|
|
677
688
|
// several keys), the every-key laws and flags at every key they read a map at (a law about several keys at tuples
|
|
678
689
|
// relating `focus`, the goals' own keys).
|
|
679
690
|
function instancesFor(m, laws, build, opts = {}) {
|
|
680
|
-
|
|
691
|
+
const seed = opts.seed && [...new Map(opts.seed.map(e => [JSON.stringify(e), e])).values()];
|
|
692
|
+
let inst = opts.start ?? (seed ? { ...Object.fromEntries(laws.filter(l => l.forall).map(l => [l.name, l.keys?.length ? [] : seed])), ...flagInstances(m, seed, opts.focus) } : {});
|
|
681
693
|
const reads = opts.reads ?? (!opts.seed || shifts(m) || laws.some(l => l.keys?.length) || pairFlags(m));
|
|
682
694
|
for (let round = 0; round < 2; round++) {
|
|
683
695
|
if (m.facts?.length)
|
|
@@ -703,7 +715,7 @@ const union = (a, b, m) => {
|
|
|
703
715
|
for (const [n, keys] of Object.entries(b)) {
|
|
704
716
|
const k = arity(m, [], n), groups = (ks) => Array.from({ length: Math.floor(ks.length / k) }, (_, t) => ks.slice(t * k, (t + 1) * k));
|
|
705
717
|
const seen = new Set(groups(out[n] ?? []).map(g => JSON.stringify(g)));
|
|
706
|
-
out[n] = [...(out[n] ?? []), ...groups(keys).filter(g => !seen.has(JSON.stringify(g))).flat()];
|
|
718
|
+
out[n] = [...(out[n] ?? []), ...groups(keys).filter(g => !seen.has(JSON.stringify(g)) && !!seen.add(JSON.stringify(g))).flat()];
|
|
707
719
|
}
|
|
708
720
|
return out;
|
|
709
721
|
};
|
|
@@ -814,7 +826,8 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
|
|
|
814
826
|
// Each step law its own instances, from its own goal: the keys the step and the laws read, and its own (a k-step
|
|
815
827
|
// law's later frames read keys no other step law has); where positions shift or a law is about several keys, the
|
|
816
828
|
// keys its goal reads a map at too. A frame's hypotheses read no name the run introduces later (kernel.ts's
|
|
817
|
-
// stepZone), so a k-step law drops the keys that read one
|
|
829
|
+
// stepZone), so a k-step law drops the keys that read one; but a flag that holds at every key holds at a later step's
|
|
830
|
+
// key too: where these instances don't prove it, each flag at its own witness at every step is tried (keptAcross).
|
|
818
831
|
const shared = [...readKeys(plain, laws), ...witnesses(plain)], named = [...laws, ...(plain.facts ?? [])], reads = shifts(plain) || laws.some(l => l.keys?.length) || pairFlags(plain);
|
|
819
832
|
const own = (sl) => [...[sl.then, ...(sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when])].flatMap(readKeysC),
|
|
820
833
|
...[sl.forall, ...(sl.keys ?? [])].flatMap(key => (key ? [{ op: 'var', name: key }] : []))];
|
|
@@ -828,7 +841,17 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
|
|
|
828
841
|
let nodes = base.nodes, leaves = base.leaves;
|
|
829
842
|
for (const sl of stepLaws)
|
|
830
843
|
for (const goal of stepLawObligations(m, laws, perStep[sl.name] ?? instances, sl)) {
|
|
831
|
-
|
|
844
|
+
let out = grow(goal);
|
|
845
|
+
// A k-step law that reads a condition kept across its steps: each flag at its own witnesses, at every step.
|
|
846
|
+
if (!('tree' in out))
|
|
847
|
+
for (const kept of keptAcross(plain, laws, perStep[sl.name] ?? instances, own(sl), sl.steps ?? 1)) {
|
|
848
|
+
const again = grow(stepLawObligations(m, laws, kept, sl)[0]);
|
|
849
|
+
if ('tree' in again) {
|
|
850
|
+
out = again;
|
|
851
|
+
perStep[sl.name] = kept;
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
832
855
|
if (!('tree' in out))
|
|
833
856
|
return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
|
|
834
857
|
trees[goal.name] = out.tree;
|