gdp-wired 0.14.2 → 0.16.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 -13
- package/dist/core/chart.js +4 -4
- package/dist/core/discover.d.ts +27 -0
- package/dist/core/discover.js +491 -0
- package/dist/core/dsl.d.ts +67 -4
- package/dist/core/dsl.js +157 -17
- package/dist/core/infer.d.ts +29 -0
- package/dist/core/infer.js +681 -0
- package/dist/kernel/kernel.d.ts +5 -1
- package/dist/kernel/kernel.js +44 -12
- package/dist/kernel/prove.d.ts +8 -0
- package/dist/kernel/prove.js +173 -15
- package/dist/lean-kernel/emit.d.ts +2 -1
- package/dist/lean-kernel/emit.js +37 -23
- package/dist/lean-kernel/library.js +1 -1
- package/package.json +10 -2
package/dist/core/dsl.d.ts
CHANGED
|
@@ -299,6 +299,26 @@ export declare class ListRef {
|
|
|
299
299
|
within(): Forall;
|
|
300
300
|
increasing(): Forall;
|
|
301
301
|
}
|
|
302
|
+
export declare class SeqRef {
|
|
303
|
+
private readonly map;
|
|
304
|
+
readonly len: Term;
|
|
305
|
+
private readonly base?;
|
|
306
|
+
constructor(map: MapRef, len: Term, base?: Term | undefined);
|
|
307
|
+
at(i: TermLike): Term;
|
|
308
|
+
private agree;
|
|
309
|
+
prefixOf(o: SeqRef): Cond;
|
|
310
|
+
startsWith(o: SeqRef): Cond;
|
|
311
|
+
equals(o: SeqRef): Cond;
|
|
312
|
+
extends(o: SeqRef, by: TermLike): Cond;
|
|
313
|
+
has(x: TermLike): Cond;
|
|
314
|
+
}
|
|
315
|
+
export declare class SeqTable {
|
|
316
|
+
readonly name: string;
|
|
317
|
+
readonly rows: Term;
|
|
318
|
+
constructor(name: string, rows: Term);
|
|
319
|
+
row(r: TermLike): SeqRef;
|
|
320
|
+
}
|
|
321
|
+
export declare const everyBelow: (n: TermLike, f: (key: Term) => Cond) => Cond;
|
|
302
322
|
export type PartsWrite = {
|
|
303
323
|
readonly parts: Record<string, MapWrite>;
|
|
304
324
|
};
|
|
@@ -440,6 +460,14 @@ export type RecurDef<A extends string, U extends Record<string, FnDef>> = {
|
|
|
440
460
|
export declare const recur: <const A extends string = never, U extends Record<string, FnDef> = {}>(name: string, def: RecurDef<A, U>, lemmas?: Record<string, (x: Term, fx: Term, env: RecurEnv<A, U>) => Cond>, between?: Record<string, (x: Term, fx: Term, y: Term, fy: Term, env: RecurEnv<A, U>) => Cond>) => FnDef & {
|
|
441
461
|
verdict: Verdict;
|
|
442
462
|
};
|
|
463
|
+
type RecurLemma = (x: Term, fx: Term, env: Record<string, Term>) => Cond;
|
|
464
|
+
type RecurBetween = (x: Term, fx: Term, y: Term, fy: Term, env: Record<string, Term>) => Cond;
|
|
465
|
+
export declare const recurOf: (d: FnDef) => {
|
|
466
|
+
name: string;
|
|
467
|
+
def: RecurDef<string, Record<string, FnDef>>;
|
|
468
|
+
lemmas: Record<string, RecurLemma>;
|
|
469
|
+
between: Record<string, RecurBetween>;
|
|
470
|
+
} | undefined;
|
|
443
471
|
type Kinds = Record<string, Kind<unknown>>;
|
|
444
472
|
type ValuesOf<K extends Kinds> = {
|
|
445
473
|
[n in keyof K]: K[n] extends Kind<infer V> ? V : never;
|
|
@@ -465,6 +493,8 @@ type Spec = {
|
|
|
465
493
|
nodes?: [string, Kind<unknown>, string[], Record<string, NodeFold<unknown, string>>][];
|
|
466
494
|
kinded?: [string, Kind<unknown>][];
|
|
467
495
|
carried?: string[];
|
|
496
|
+
seqs?: string[];
|
|
497
|
+
seqTables?: string[];
|
|
468
498
|
lists2?: [string, Kind<unknown>, Record<string, ListFold<unknown>>, Record<string, ListLemma>][];
|
|
469
499
|
fns?: [string, FnDef][];
|
|
470
500
|
facts?: ((env: never) => Record<string, Cond | Forall>)[];
|
|
@@ -487,6 +517,12 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
487
517
|
constructor(spec: Spec);
|
|
488
518
|
private with;
|
|
489
519
|
get name(): string;
|
|
520
|
+
get parts(): {
|
|
521
|
+
params: [string, Kind<unknown>][];
|
|
522
|
+
fns: [string, FnDef][];
|
|
523
|
+
lists: [string, Kind<unknown>, Record<string, ListFold<unknown>>, Record<string, ListLemma>][];
|
|
524
|
+
};
|
|
525
|
+
withParts(fns: Record<string, FnDef>, listLemmas?: Record<string, Record<string, ListLemma>>): this;
|
|
490
526
|
params<K extends Kinds>(kinds: K): Rule<P & ValuesOf<K>, I, S, M, F, R, Su, Q>;
|
|
491
527
|
inputs<K extends Kinds>(kinds: K): Rule<P, I & ValuesOf<K>, S, M, F, R, Su, Q>;
|
|
492
528
|
state<T extends Record<string, number | ((p: P) => TermLike) | Init<unknown>>>(init: T): Rule<P, I, S & {
|
|
@@ -525,6 +561,12 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
525
561
|
carries<const N extends string>(...names: N[]): Rule<P, I & {
|
|
526
562
|
[k in N]: MapRef;
|
|
527
563
|
}, S, M, F, R, Su, Q>;
|
|
564
|
+
carriesSeq<const N extends string>(...names: N[]): Rule<P, I & {
|
|
565
|
+
[k in N]: SeqRef;
|
|
566
|
+
}, S, M, F, R, Su, Q>;
|
|
567
|
+
carriesSeqs<const N extends string>(...names: N[]): Rule<P, I & {
|
|
568
|
+
[k in N]: SeqTable;
|
|
569
|
+
}, S, M, F, R, Su, Q>;
|
|
528
570
|
carriesList<const N extends string, V, Fd extends string = never>(name: N, of: Kind<V>, folds?: Record<Fd, ListFold<V, P>>, lemmas?: Record<string, (acc: Record<Fd, Term>, i: Term, p: P) => Cond>): Rule<P, I & {
|
|
529
571
|
[k in N]: CarriedList<V, Fd>;
|
|
530
572
|
}, S, M, F, R, Su, Q>;
|
|
@@ -590,7 +632,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
590
632
|
[k in keyof P]: Raw;
|
|
591
633
|
}): Record<string, number>;
|
|
592
634
|
encodeInput(values: {
|
|
593
|
-
[k in keyof I]: I[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : I[k] extends CarriedList<any, any> ? readonly Raw[] : Raw;
|
|
635
|
+
[k in keyof I]: I[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : I[k] extends SeqRef ? readonly number[] : I[k] extends SeqTable ? readonly (readonly number[])[] : I[k] extends CarriedList<any, any> ? readonly Raw[] : Raw;
|
|
594
636
|
}, params?: {
|
|
595
637
|
[k in keyof P]: Raw;
|
|
596
638
|
}): Request;
|
|
@@ -639,6 +681,10 @@ export type Decision = {
|
|
|
639
681
|
given: C[];
|
|
640
682
|
then: C;
|
|
641
683
|
goal: Goal;
|
|
684
|
+
steps?: {
|
|
685
|
+
rule: Rule<Obj>;
|
|
686
|
+
law: StepLaw;
|
|
687
|
+
};
|
|
642
688
|
};
|
|
643
689
|
type ResultOf<V> = 0 extends 1 & V ? Term : [V] extends [Cond] ? Cond : [V] extends [Decimal] ? Decimal : [V] extends [Int] ? Int : [V] extends [Choice<infer K>] ? Choice<K> : [V] extends [Str] ? Str : [V] extends [Variant<infer C, infer T>] ? Variant<C, T> : [V] extends [Maybe<infer W>] ? Maybe<ResultOf<W>> : [V] extends [Term | number] ? Term : [V] extends [Record<string, unknown>] ? {
|
|
644
690
|
[f in keyof V]: ResultOf<V[f]>;
|
|
@@ -660,7 +706,7 @@ type Value<T> = {
|
|
|
660
706
|
[k in keyof T]: ValueOf<T[k]>;
|
|
661
707
|
};
|
|
662
708
|
type RawInputs<T> = {
|
|
663
|
-
[k in keyof T]: T[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : T[k] extends CarriedList<infer V, infer _F> ? readonly ValueOf<V>[] : ValueOf<T[k]>;
|
|
709
|
+
[k in keyof T]: T[k] extends MapRef ? ReadonlySet<number> | readonly number[] | ReadonlyMap<number, number> : T[k] extends SeqRef ? readonly number[] : T[k] extends SeqTable ? readonly (readonly number[])[] : T[k] extends CarriedList<infer V, infer _F> ? readonly ValueOf<V>[] : ValueOf<T[k]>;
|
|
664
710
|
};
|
|
665
711
|
type Field = Term | Cond | Int | Decimal | Choice<string> | Str | Variant<any, any> | Maybe<any> | {
|
|
666
712
|
[f: string]: Field | number;
|
|
@@ -752,13 +798,30 @@ export type Verdict = {
|
|
|
752
798
|
params: Record<string, number>;
|
|
753
799
|
trace: Record<string, unknown>[];
|
|
754
800
|
};
|
|
801
|
+
found?: Found;
|
|
802
|
+
};
|
|
803
|
+
export type Found = {
|
|
804
|
+
laws?: {
|
|
805
|
+
law: Law;
|
|
806
|
+
text: string;
|
|
807
|
+
source?: string;
|
|
808
|
+
}[];
|
|
809
|
+
lemmas?: Record<string, {
|
|
810
|
+
lemmas?: string[];
|
|
811
|
+
between?: string[];
|
|
812
|
+
}>;
|
|
813
|
+
};
|
|
814
|
+
export type ProveOptions = {
|
|
815
|
+
search?: boolean | {
|
|
816
|
+
ms?: number;
|
|
817
|
+
};
|
|
755
818
|
};
|
|
756
819
|
export declare class Unproven extends Error {
|
|
757
820
|
readonly verdict: Verdict;
|
|
758
821
|
constructor(what: string, verdict: Verdict);
|
|
759
822
|
}
|
|
760
|
-
export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[]): Verdict;
|
|
761
|
-
export declare function proveSteps(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], stepLaws: StepLaw[]): Verdict;
|
|
823
|
+
export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], opts?: ProveOptions): Verdict;
|
|
824
|
+
export declare function proveSteps(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], stepLaws: StepLaw[], opts?: ProveOptions): Verdict;
|
|
762
825
|
export type { StepLaw };
|
|
763
826
|
export type { Liveness };
|
|
764
827
|
export declare function proveLeadsTo(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], l: Liveness): Verdict;
|
package/dist/core/dsl.js
CHANGED
|
@@ -19,6 +19,8 @@ import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersO
|
|
|
19
19
|
import { allowed, keysOf, requestEnv, watch } from "./watch.js";
|
|
20
20
|
import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
|
|
21
21
|
import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws } from "../kernel/prove.js";
|
|
22
|
+
import { discover } from "./discover.js";
|
|
23
|
+
import { inferLaws } from "./infer.js";
|
|
22
24
|
// --- terms and conditions -------------------------------------------------------------------------------------------
|
|
23
25
|
const naturalConst = (v) => {
|
|
24
26
|
if (!Number.isSafeInteger(v) || v < 0)
|
|
@@ -860,6 +862,43 @@ export class ListRef {
|
|
|
860
862
|
within() { return forall(k => this.len.atMost(k).implies(this.at(k).eq(0))); }
|
|
861
863
|
increasing() { return forall((i, j) => i.lt(j).and(j.lt(this.len)).implies(this.at(i).lt(this.at(j)))); }
|
|
862
864
|
}
|
|
865
|
+
// A sequence of naturals as positions and a length (a path's segments, as interned names): `at(i)` its value at i,
|
|
866
|
+
// `len` its length. Comparing two is a condition about every position below a length (the array property fragment,
|
|
867
|
+
// decidable), so a law about prefixes needs no induction. One a request carries (`carriesSeq`), or a row of a table of
|
|
868
|
+
// them (`carriesSeqs`), its positions read from its row's offset in the table's one map.
|
|
869
|
+
export class SeqRef {
|
|
870
|
+
map;
|
|
871
|
+
len;
|
|
872
|
+
base;
|
|
873
|
+
constructor(map, len, base) {
|
|
874
|
+
this.map = map;
|
|
875
|
+
this.len = len;
|
|
876
|
+
this.base = base;
|
|
877
|
+
}
|
|
878
|
+
at(i) { return this.map.at(!this.base ? i : typeof i === 'number' ? this.base.plus(i) : i.plus(this.base)); }
|
|
879
|
+
// The same values at every position below `upTo` (both have them).
|
|
880
|
+
agree(o, upTo) { return everyKey(k => k.lt(upTo).implies(this.at(k).eq(o.at(k)))); }
|
|
881
|
+
// This one is where o starts; o is where this one starts; the same; o followed by `by` more; x at some position.
|
|
882
|
+
prefixOf(o) { return this.len.atMost(o.len).and(this.agree(o, this.len)); }
|
|
883
|
+
startsWith(o) { return o.prefixOf(this); }
|
|
884
|
+
equals(o) { return this.len.eq(o.len).and(this.agree(o, this.len)); }
|
|
885
|
+
extends(o, by) { return o.prefixOf(this).and(this.len.eq(o.len.plus(by))); }
|
|
886
|
+
has(x) { return everyKey(k => k.lt(this.len).implies(this.at(k).ne(x))).not(); }
|
|
887
|
+
}
|
|
888
|
+
// A table of sequences a request carries (each grant's path, say): `row(r)` is row r's sequence, `rows` how many. Rows
|
|
889
|
+
// are read by their number as a map's key, so a condition about every row reads a row only there (`everyBelow(T.rows,
|
|
890
|
+
// r => ...)`).
|
|
891
|
+
export class SeqTable {
|
|
892
|
+
name;
|
|
893
|
+
rows;
|
|
894
|
+
constructor(name, rows) {
|
|
895
|
+
this.name = name;
|
|
896
|
+
this.rows = rows;
|
|
897
|
+
}
|
|
898
|
+
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)); }
|
|
899
|
+
}
|
|
900
|
+
// f at every key below n: what bookkeeping a request carries meets, row by row (where each grant went, say).
|
|
901
|
+
export const everyBelow = (n, f) => everyKey(k => k.lt(n).implies(f(k)));
|
|
863
902
|
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
903
|
const isPartsWrite = (u) => typeof u === 'object' && u !== null && 'parts' in u;
|
|
865
904
|
// The parts of a value written at a cell, by the names of the maps they go to.
|
|
@@ -995,7 +1034,7 @@ export class CarriedList {
|
|
|
995
1034
|
.state({ i: 0, ...Object.fromEntries(folds.map(([f, d]) => [`acc$${f}`, typeof d.from === 'number' ? d.from : (p) => d.from(p)])) })
|
|
996
1035
|
.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
1036
|
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))));
|
|
1037
|
+
return proveRule(r, Object.entries(this.lemmas).map(([n, l]) => r.law(n, (env) => l(accs(env), env.i, env))), { search: false });
|
|
999
1038
|
}
|
|
1000
1039
|
// Every carried map it is kept in: the items' parts, then each fold's.
|
|
1001
1040
|
get maps() { return [...this.kind.vars(this.name), ...Object.keys(this.folds).map(f => `${this.name}$${f}`)]; }
|
|
@@ -1374,7 +1413,7 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1374
1413
|
const r = r0.state({ x: 0, fx: ((p) => (zeroReads ? p.fx0 : zeroAt(args.map(a => p[a])))) })
|
|
1375
1414
|
.step((env) => ({ x: env.x.plus(1), fx: def.next(env.x, env.fx, envAt(args.map(a => env[a]))) }));
|
|
1376
1415
|
const laws = Object.entries(lemmas).map(([n, f]) => r.law(n, (env) => f(env.x, env.fx, envAt(args.map(a => env[a])))));
|
|
1377
|
-
verdict = proveRule(r, laws);
|
|
1416
|
+
verdict = proveRule(r, laws, { search: false });
|
|
1378
1417
|
failed('lemmas', verdict);
|
|
1379
1418
|
proofs.lemmas = { machine: r.machine, laws };
|
|
1380
1419
|
}
|
|
@@ -1411,13 +1450,17 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
|
|
|
1411
1450
|
.step((env) => ({ y: env.y.plus(1), fy: def.next(env.y, env.fy, envAt(at(env))) }));
|
|
1412
1451
|
const laws = [...Object.entries(lemmas).map(([n, l]) => r.law(`${n} at y`, (env) => l(env.y, env.fy, envAt(at(env))))),
|
|
1413
1452
|
...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);
|
|
1453
|
+
const v = proveRule(r, laws, { search: false });
|
|
1415
1454
|
failed('laws between two points', v);
|
|
1416
1455
|
proofs.between = { machine: r.machine, laws };
|
|
1417
1456
|
verdict = { proven: true, checked: verdict.checked && v.checked };
|
|
1418
1457
|
}
|
|
1419
|
-
|
|
1458
|
+
const out = { ...base, verdict, facts: facts(true) };
|
|
1459
|
+
recurDefs.set(out, { name, def: def, lemmas: lemmas, between: between });
|
|
1460
|
+
return out;
|
|
1420
1461
|
};
|
|
1462
|
+
const recurDefs = new WeakMap();
|
|
1463
|
+
export const recurOf = (d) => recurDefs.get(d);
|
|
1421
1464
|
const distinctNames = (what, names) => { if (new Set(names).size !== names.length)
|
|
1422
1465
|
throw new Error(`${what}: a name given twice`); };
|
|
1423
1466
|
// A recurrence as JavaScript: a function of its arguments, the functions it reads inlined (each called on its arguments,
|
|
@@ -1527,6 +1570,21 @@ export class Rule {
|
|
|
1527
1570
|
}
|
|
1528
1571
|
with(more) { return new Rule({ ...this.spec, ...more }); }
|
|
1529
1572
|
get name() { return this.spec.name; }
|
|
1573
|
+
// For core/discover.ts: the functions and carried lists the rule reads, and the same rule with functions made again
|
|
1574
|
+
// and lemmas added to its lists' folds (each proven here, as carriesList proves its own).
|
|
1575
|
+
get parts() {
|
|
1576
|
+
return { params: this.spec.params, fns: this.spec.fns ?? [], lists: this.spec.lists2 ?? [] };
|
|
1577
|
+
}
|
|
1578
|
+
withParts(fns, listLemmas = {}) {
|
|
1579
|
+
const lists = (this.spec.lists2 ?? []).map(([n, k, fs, ls]) => [n, k, fs, { ...ls, ...(listLemmas[n] ?? {}) }]);
|
|
1580
|
+
for (const [n, k, fs, ls] of lists)
|
|
1581
|
+
if (listLemmas[n]) {
|
|
1582
|
+
const v2 = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, undefined, this.spec.params).prove();
|
|
1583
|
+
if (!(v2.proven && v2.checked))
|
|
1584
|
+
throw new Unproven(`${n}'s fold lemmas`, v2);
|
|
1585
|
+
}
|
|
1586
|
+
return this.with({ fns: (this.spec.fns ?? []).map(([n, d]) => [n, fns[n] ?? d]), lists2: lists });
|
|
1587
|
+
}
|
|
1530
1588
|
params(kinds) { return this.with({ params: [...this.spec.params, ...Object.entries(kinds)] }); }
|
|
1531
1589
|
inputs(kinds) { return this.with({ inputs: [...this.spec.inputs, ...Object.entries(kinds)] }); }
|
|
1532
1590
|
// Each variable starts at a natural or at a term over the params, or is declared by a kind and starts where `from`
|
|
@@ -1612,6 +1670,16 @@ export class Rule {
|
|
|
1612
1670
|
facts(f) { return this.with({ facts: [...(this.spec.facts ?? []), f] }); }
|
|
1613
1671
|
// Maps each request carries whole (a set of names, say: cells 0 or 1), every cell past them 0; read like any map.
|
|
1614
1672
|
carries(...names) { return this.with({ carried: [...(this.spec.carried ?? []), ...names] }); }
|
|
1673
|
+
// Sequences each request carries (SeqRef): `.carriesSeq('Path')`, then `Path.at(i)`, `Path.len`, `A.prefixOf(B)`; the
|
|
1674
|
+
// request gives each as an array of naturals.
|
|
1675
|
+
carriesSeq(...names) {
|
|
1676
|
+
return this.with({ carried: [...(this.spec.carried ?? []), ...names], inputs: [...this.spec.inputs, ...names.map(n => [`${n}_len`, nat])], seqs: [...(this.spec.seqs ?? []), ...names] });
|
|
1677
|
+
}
|
|
1678
|
+
// Tables of sequences each request carries (SeqTable): `.carriesSeqs('Paths')`, then `Paths.row(r)`, `Paths.rows`; the
|
|
1679
|
+
// request gives each as an array of arrays of naturals.
|
|
1680
|
+
carriesSeqs(...names) {
|
|
1681
|
+
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] });
|
|
1682
|
+
}
|
|
1615
1683
|
// A list of values of a kind each request carries, with folds over it and lemmas about the folds (CarriedList):
|
|
1616
1684
|
// .carriesList('Lines', record({amount: nat}), {total: {from: 0, step: (acc, line) => acc.plus(line.amount)}})
|
|
1617
1685
|
// Each fold's start and step, and each lemma, may read the params declared before it.
|
|
@@ -1758,6 +1826,8 @@ export class Rule {
|
|
|
1758
1826
|
// A decision: whenever the params and the request meet `given`, `then` holds. Decisions don't read the state; the
|
|
1759
1827
|
// functions' facts are assumed at the arguments the decision reads them at, and a carried list's equations (each
|
|
1760
1828
|
// 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.
|
|
1829
|
+
// A condition about every key (`everyKey`, `subsetOf`, ...) in `given` or `then` makes the decision a step law of
|
|
1830
|
+
// this rule (its conditions as flags, the step left as it is): the flags' quantifiers are what the kernel closes.
|
|
1761
1831
|
decide(name, given, then) {
|
|
1762
1832
|
const env = this.env({ flags: false, state: false, queries: false });
|
|
1763
1833
|
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)];
|
|
@@ -1766,7 +1836,14 @@ export class Rule {
|
|
|
1766
1836
|
const listed = lists.flatMap(l => l.equationsAt(uniqueTerms([l.len, l.len.minus(1), ...keysRead([...hyps, t], l.maps)])).map(c => c.c));
|
|
1767
1837
|
const goal = { name, hyps: [...listed, ...hyps, { op: 'not', x: t }] };
|
|
1768
1838
|
const facts = this.spec.fns?.length || this.spec.facts?.length ? factsFor(this.machine, goal) : [];
|
|
1769
|
-
|
|
1839
|
+
const d = { name, rule: this, given: hyps, then: t, goal: { name, hyps: [...facts, ...goal.hyps] } };
|
|
1840
|
+
if ([...(Array.isArray(g) ? g : [g]).map(c => c.c), t].some(quantifiedIn)) {
|
|
1841
|
+
const conds = (e) => { const own = given(e); return { given$: all(...(Array.isArray(own) ? own : [own])), then$: then(e) }; };
|
|
1842
|
+
const r = (this.spec.step ? this : this.step(() => ({}))).flags(conds);
|
|
1843
|
+
const flag = (f) => (env) => env[f];
|
|
1844
|
+
d.steps = { rule: r, law: r.stepLaw(name, flag('then$'), flag('given$')) };
|
|
1845
|
+
}
|
|
1846
|
+
return withSource(d, { decision: { given: given, then: then, own: Array.isArray(g) ? g.length : 1 } });
|
|
1770
1847
|
}
|
|
1771
1848
|
// A decision about two requests under the same params (noninterference, say: requests that differ only in a secret
|
|
1772
1849
|
// are decided alike). `given` and `then` read both; the second request's inputs are named b.<input> in a witness.
|
|
@@ -1818,6 +1895,15 @@ export class Rule {
|
|
|
1818
1895
|
out[n] = k.value(n);
|
|
1819
1896
|
for (const n of this.spec.carried ?? [])
|
|
1820
1897
|
out[n] = new MapRef(n, 0);
|
|
1898
|
+
for (const n of this.spec.seqs ?? []) {
|
|
1899
|
+
delete out[`${n}_len`];
|
|
1900
|
+
out[n] = new SeqRef(new MapRef(n, 0), v(`${n}_len`));
|
|
1901
|
+
}
|
|
1902
|
+
for (const n of this.spec.seqTables ?? []) {
|
|
1903
|
+
for (const part of [`${n}_base`, `${n}_len`, `${n}_rows`])
|
|
1904
|
+
delete out[part];
|
|
1905
|
+
out[n] = new SeqTable(n, v(`${n}_rows`));
|
|
1906
|
+
}
|
|
1821
1907
|
const ps = Object.fromEntries(this.spec.params.map(([n]) => [n, out[n]]));
|
|
1822
1908
|
for (const [n, k, fs, ls] of this.spec.lists2 ?? []) {
|
|
1823
1909
|
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, ps, this.spec.params);
|
|
@@ -2159,16 +2245,35 @@ export class Rule {
|
|
|
2159
2245
|
// --- running it -----------------------------------------------------------------------------------------------------
|
|
2160
2246
|
// Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
|
|
2161
2247
|
encodeParams(values) { return encode(this.spec.params, values); }
|
|
2162
|
-
// A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural
|
|
2248
|
+
// 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
|
|
2249
|
+
// naturals, a table of them an array of such arrays.
|
|
2163
2250
|
// A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
|
|
2164
2251
|
// folds that read the params are computed with.
|
|
2165
2252
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2166
2253
|
encodeInput(values, params) {
|
|
2167
2254
|
const at = params && this.paramValues(this.encodeParams(params));
|
|
2168
2255
|
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])]));
|
|
2169
|
-
|
|
2256
|
+
// A sequence: its values at their positions (no 0 cell), its length; a table: the rows one after another in one map,
|
|
2257
|
+
// each row's offset and length.
|
|
2258
|
+
const seqMaps = {}, seqLens = {};
|
|
2259
|
+
const cells = (xs, from = 0) => xs.flatMap((x, i) => (x ? [[from + i, x]] : []));
|
|
2260
|
+
for (const n of this.spec.seqs ?? []) {
|
|
2261
|
+
const xs = values[n] ?? [];
|
|
2262
|
+
seqMaps[n] = new Map(cells(xs));
|
|
2263
|
+
seqLens[`${n}_len`] = xs.length;
|
|
2264
|
+
}
|
|
2265
|
+
for (const n of this.spec.seqTables ?? []) {
|
|
2266
|
+
const rows = values[n] ?? [], base = [];
|
|
2267
|
+
rows.reduce((at, r) => (base.push(at), at + r.length), 0);
|
|
2268
|
+
seqMaps[n] = new Map(rows.flatMap((r, i) => cells(r, base[i])));
|
|
2269
|
+
seqMaps[`${n}_base`] = new Map(base.flatMap((b, i) => (b ? [[i, b]] : [])));
|
|
2270
|
+
seqMaps[`${n}_len`] = new Map(rows.flatMap((r, i) => (r.length ? [[i, r.length]] : [])));
|
|
2271
|
+
seqLens[`${n}_rows`] = rows.length;
|
|
2272
|
+
}
|
|
2273
|
+
const out = encode(this.spec.inputs, { ...values, ...seqLens, ...Object.fromEntries(Object.entries(lists).map(([n, l]) => [`${n}_len`, l.len])) });
|
|
2170
2274
|
for (const l of Object.values(lists))
|
|
2171
2275
|
Object.assign(out, l.maps);
|
|
2276
|
+
Object.assign(out, seqMaps);
|
|
2172
2277
|
for (const n of this.spec.carried ?? []) {
|
|
2173
2278
|
if (n in out)
|
|
2174
2279
|
continue;
|
|
@@ -2249,6 +2354,8 @@ const expandParts = (updates) => {
|
|
|
2249
2354
|
};
|
|
2250
2355
|
const encode = (kinds, values) => Object.assign({}, ...kinds.map(([n, k]) => k.encode(n, values[n])));
|
|
2251
2356
|
export const rule = (name) => new Rule({ name, params: [], inputs: [], state: [], maps: [], flags: [], pre: [], assume: [], sums: [], queries: [] });
|
|
2357
|
+
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;
|
|
2358
|
+
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));
|
|
2252
2359
|
// The kind a result field is kept in.
|
|
2253
2360
|
const kindOf = (t) => (t instanceof Cond ? bool : t instanceof Int ? int : t instanceof Decimal ? decimal(t.places) : t instanceof Choice ? oneOf(...t.values) : t instanceof Str ? str({ like: t.patterns })
|
|
2254
2361
|
: t instanceof Variant ? t.kind : t instanceof Maybe ? maybe(kindOf(t.inner)) : isRecord(t) ? record(Object.fromEntries(Object.entries(t).map(([f, x]) => [f, kindOf(x)]))) : nat);
|
|
@@ -2402,14 +2509,16 @@ const withKinds = (r, laws) => [...r.kindLaws().filter(k => !laws.some(l => l.na
|
|
|
2402
2509
|
// interpreter took. A rule that reads functions is skipped: the kernel knows them only by their facts (a recurrence's
|
|
2403
2510
|
// induction machine reads the function it proves things about), so a run computing them proves nothing about the goal.
|
|
2404
2511
|
const refuteBudget = { trials: 60, depth: 6, bound: 3, ms: 150 };
|
|
2405
|
-
|
|
2512
|
+
// The search's own runs, before it looks for what the laws need: deeper and wider, from another seed.
|
|
2513
|
+
const deeperBudget = { trials: 600, depth: 24, bound: 8, ms: 1500, seed: 29 };
|
|
2514
|
+
function refuteByRuns(r, laws, stepLaws = [], budget = refuteBudget) {
|
|
2406
2515
|
const m = r.machine;
|
|
2407
2516
|
if (Object.keys(m.fns ?? {}).length || m.facts?.length)
|
|
2408
2517
|
return undefined;
|
|
2409
|
-
const run = interpret(m), rand = seeded(13), until = performance.now() +
|
|
2518
|
+
const run = interpret(m), rand = seeded(budget.seed ?? 13), until = performance.now() + budget.ms, bound = budget.bound;
|
|
2410
2519
|
const shown = (input) => Object.fromEntries(Object.entries(input).map(([n, x]) => [n, typeof x === 'number' ? x : Object.fromEntries(x)]));
|
|
2411
2520
|
try {
|
|
2412
|
-
for (let trial = 0; trial <
|
|
2521
|
+
for (let trial = 0; trial < budget.trials && performance.now() < until; trial++) {
|
|
2413
2522
|
const params = r.randomParams(rand, bound);
|
|
2414
2523
|
if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
|
|
2415
2524
|
continue;
|
|
@@ -2421,7 +2530,7 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2421
2530
|
for (const { w, on } of watching)
|
|
2422
2531
|
if (!('then' in w) && !on.start(st))
|
|
2423
2532
|
return broken(w, '@start', st);
|
|
2424
|
-
for (let d = 0, tries = 0; d <
|
|
2533
|
+
for (let d = 0, tries = 0; d < budget.depth && tries < 40; tries++) {
|
|
2425
2534
|
const input = r.randomInput(rand, bound, params);
|
|
2426
2535
|
if (!allowed(m, run, params, st, input))
|
|
2427
2536
|
continue;
|
|
@@ -2441,7 +2550,7 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2441
2550
|
// variable of a kind is drawn as its kind draws (33 yes/nos drawn from 0..7 almost never make a lawful state).
|
|
2442
2551
|
const steps1 = stepLaws.filter(sl => (sl.steps ?? 1) === 1), given = withKinds(r, laws);
|
|
2443
2552
|
const wide = Object.keys(m.state).length + m.inputs.length >= 32, lawsHere = wide ? given : [], kinded = wide ? r.kindsOfState() : [];
|
|
2444
|
-
for (let trial = 0; !m.history && (steps1.length || lawsHere.length) && trial <
|
|
2553
|
+
for (let trial = 0; !m.history && (steps1.length || lawsHere.length) && trial < budget.trials * 4 && performance.now() < until; trial++) {
|
|
2445
2554
|
const params = r.randomParams(rand, bound);
|
|
2446
2555
|
if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
|
|
2447
2556
|
continue;
|
|
@@ -2469,25 +2578,56 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2469
2578
|
return undefined;
|
|
2470
2579
|
}
|
|
2471
2580
|
const refutedVerdict = (out) => ({ proven: false, checked: false, goal: out.goal, stuck: 'sat', witness: out.witness, ...(out.run ? { run: out.run } : {}) });
|
|
2472
|
-
export function proveRule(r, laws) {
|
|
2581
|
+
export function proveRule(r, laws, opts = {}) {
|
|
2473
2582
|
const refuted = refuteByRuns(r, laws);
|
|
2474
2583
|
if (refuted)
|
|
2475
2584
|
return refutedVerdict(refuted);
|
|
2476
2585
|
const out = proveLaws(r.machine, withKinds(r, laws));
|
|
2477
|
-
|
|
2586
|
+
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 } : {}) } : {}) };
|
|
2587
|
+
return searched(r, laws, [], v, opts);
|
|
2478
2588
|
}
|
|
2479
|
-
export function proveSteps(r, laws, stepLaws) {
|
|
2589
|
+
export function proveSteps(r, laws, stepLaws, opts = {}) {
|
|
2480
2590
|
const refuted = refuteByRuns(r, laws, stepLaws);
|
|
2481
2591
|
if (refuted)
|
|
2482
2592
|
return refutedVerdict(refuted);
|
|
2483
2593
|
const out = proveStepLaws(r.machine, withKinds(r, laws), stepLaws);
|
|
2484
|
-
|
|
2594
|
+
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 } : {}) } : {}) };
|
|
2595
|
+
return searched(r, laws, stepLaws, v, opts);
|
|
2596
|
+
}
|
|
2597
|
+
// The search, where the prover alone didn't prove the laws and nothing refuted them: the lemmas first where the rule has
|
|
2598
|
+
// recurrences or carried lists (the rule made again with them), then invariants over what it then is. The verdict is the
|
|
2599
|
+
// prover's on the laws with what was found; when nothing is found, the prover's own (with why).
|
|
2600
|
+
function searched(r, laws, stepLaws, v, opts) {
|
|
2601
|
+
if ((v.proven && v.checked) || opts.search === false || v.run || v.stuck === 'sat' && !/@step|@\d+ steps/.test(v.goal ?? ''))
|
|
2602
|
+
return v;
|
|
2603
|
+
const deeper = refuteByRuns(r, laws, stepLaws, deeperBudget);
|
|
2604
|
+
if (deeper?.run)
|
|
2605
|
+
return refutedVerdict(deeper);
|
|
2606
|
+
const until = performance.now() + (typeof opts.search === 'object' && opts.search.ms !== undefined ? opts.search.ms : 10_000);
|
|
2607
|
+
let rule = r, lemmas;
|
|
2608
|
+
const { fns, lists } = r.parts;
|
|
2609
|
+
if (lists.length || fns.some(([, d]) => recurOf(d))) {
|
|
2610
|
+
const d = discover(r, laws, stepLaws, v);
|
|
2611
|
+
if (d.verdict.proven && d.verdict.checked)
|
|
2612
|
+
return { ...d.verdict, found: { lemmas: d.found } };
|
|
2613
|
+
if (Object.keys(d.found).length)
|
|
2614
|
+
(rule = d.rule, lemmas = d.found);
|
|
2615
|
+
}
|
|
2616
|
+
const ms = until - performance.now();
|
|
2617
|
+
if (ms < 500)
|
|
2618
|
+
return v;
|
|
2619
|
+
const inf = inferLaws(rule, laws, stepLaws, { ms, ...(rule === r ? { first: v } : {}) });
|
|
2620
|
+
if (!(inf.proven && inf.checked))
|
|
2621
|
+
return { ...v, why: [v.why, `the search found nothing that proves it in ${Math.round(inf.stats.ms)} ms`].filter(Boolean).join('; ') };
|
|
2622
|
+
const { found, stats: _stats, ...verdict } = inf;
|
|
2623
|
+
void _stats;
|
|
2624
|
+
return { ...verdict, found: { laws: found, ...(lemmas ? { lemmas } : {}) } };
|
|
2485
2625
|
}
|
|
2486
2626
|
export function proveLeadsTo(r, laws, l) {
|
|
2487
2627
|
const out = proveLiveness(r.machine, withKinds(r, laws), l);
|
|
2488
2628
|
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 } : {}) } : {}) };
|
|
2489
2629
|
}
|
|
2490
|
-
export const proveDecision = (d) => goalsVerdict([d.goal]);
|
|
2630
|
+
export const proveDecision = (d) => (d.steps ? proveSteps(d.steps.rule, [], [d.steps.law], { search: false }) : goalsVerdict([d.goal]));
|
|
2491
2631
|
const seeded = (seed) => { let x = seed; return k => { x = (x * 1103515245 + 12345) % 2 ** 31; return Math.floor(x / 2 ** 31 * k); }; };
|
|
2492
2632
|
// A decision's callbacks against its condition, on random params and requests: `given` and `then` each run with every
|
|
2493
2633
|
// value known, against what the interpreter evaluates of the decision built from them.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Law, type Machine, type State } from './machine.js';
|
|
2
|
+
import { type StepLaw, type Verdict, proveRule } from './dsl.js';
|
|
3
|
+
type AnyRule = Parameters<typeof proveRule>[0];
|
|
4
|
+
export type InferOptions = {
|
|
5
|
+
ms?: number;
|
|
6
|
+
runsMs?: number;
|
|
7
|
+
seen?: State[];
|
|
8
|
+
constants?: number[];
|
|
9
|
+
maxCandidates?: number;
|
|
10
|
+
first?: Verdict;
|
|
11
|
+
progress?: (phase: string, info: Record<string, number | string>) => void;
|
|
12
|
+
};
|
|
13
|
+
export type Found = {
|
|
14
|
+
law: Law;
|
|
15
|
+
text: string;
|
|
16
|
+
source?: string;
|
|
17
|
+
};
|
|
18
|
+
export type Inferred = Verdict & {
|
|
19
|
+
found: Found[];
|
|
20
|
+
stats: {
|
|
21
|
+
samples: number;
|
|
22
|
+
candidates: number;
|
|
23
|
+
alive: number;
|
|
24
|
+
proofs: number;
|
|
25
|
+
ms: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export declare function inferLaws(target: AnyRule | Machine, laws: Law[], stepLaws?: StepLaw[], opts?: InferOptions): Inferred;
|
|
29
|
+
export {};
|