gdp-wired 0.15.0 → 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 +11 -0
- package/dist/core/chart.js +4 -4
- package/dist/core/discover.d.ts +1 -1
- package/dist/core/discover.js +4 -4
- package/dist/core/dsl.d.ts +49 -4
- package/dist/core/dsl.js +125 -15
- package/dist/core/infer.js +118 -10
- package/dist/kernel/prove.js +21 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -69,6 +69,10 @@ All of these are proven automatically, and each proof is also a Lean theorem:
|
|
|
69
69
|
machine runs as, with each guard and action it names given again in gdp-wired's terms, and `conforms(chart, machine,
|
|
70
70
|
createActor)` drives XState's own actor and the compiled rule with the same random events, comparing every state and
|
|
71
71
|
context; what a chart can't run the same way is refused when built, with the reason (`examples/xstate.ts`).
|
|
72
|
+
- **What nobody stated, looked for when the prover alone stops**: where `proveRule` or `proveSteps` doesn't prove the laws
|
|
73
|
+
and no run breaks them, it looks for lemmas about the rule's recurrences and lists (`discover`) and then invariants
|
|
74
|
+
(`inferLaws`), within 10 s (`{search: {ms}}`; `{search: false}`, the prover alone); the verdict's `found` holds what
|
|
75
|
+
proved them, as laws and lemmas with the source to paste.
|
|
72
76
|
- **Invariants nobody wrote** (`gdp-wired/infer`): `inferLaws(rule, laws)` proves a law that holds but isn't inductive
|
|
73
77
|
alone, with the laws it finds (bounds, sums and differences over the state, the same where a flag or a choice holds
|
|
74
78
|
one value, and laws about every key of a map), each proven by the kernel with yours and handed back as the
|
|
@@ -246,6 +250,13 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
|
|
|
246
250
|
`Acct.set(k, value)`, `Acct.each(k => value)`) and `.items({Queue: Job})` (`push`, `pop`, `set`, `insertAt`,
|
|
247
251
|
`removeAt`, `apply`, `has`, `within`, `increasing(by)`), one map per variable of the kind; `rule.decode` reads them back
|
|
248
252
|
as objects and arrays.
|
|
253
|
+
- **Sequences a request carries** (`.carriesSeq('Path')`, and tables of them, `.carriesSeqs('Paths')`): a sequence of
|
|
254
|
+
naturals as positions and a length, `Path.at(i)`, `Path.len`, a table's row `Paths.row(r)`; `A.prefixOf(B)`,
|
|
255
|
+
`startsWith`, `equals`, `extends(B, k)`, `has(x)`, each a condition about every position below a length, so laws about
|
|
256
|
+
prefixes of paths need no induction or lemma. The request gives an array of naturals (interned names), a table an
|
|
257
|
+
array of them. `everyBelow(n, k => ...)` checks bookkeeping a request carries row by row (where each grant went). A
|
|
258
|
+
condition about every row of a table can't compare the rows' sequences (that is two keys at once); compare a row at a
|
|
259
|
+
named row, a witness, instead (`examples/scopes.ts`: pi-fabric's path scopes, covering, attenuation and normalization).
|
|
249
260
|
- **Lists a request carries** (`.carriesList('Lines', Line, folds, lemmas)`): the items one carried map per variable of
|
|
250
261
|
the kind, the length `Lines.len`, each fold (`{total: {from: 0, step: (acc, line) => acc.plus(line.amount)}}`) a
|
|
251
262
|
carried map of its running values whose equations every request is assumed to meet (the encoding computes them from the
|
package/dist/core/chart.js
CHANGED
|
@@ -256,9 +256,9 @@ export class Chart {
|
|
|
256
256
|
missed.push({ name, law: r.law(`${name} is never reached`, (env) => env[reg.field].is(l).not()) });
|
|
257
257
|
}
|
|
258
258
|
const proven = (v) => v.proven && v.checked;
|
|
259
|
-
const allMissed = missed.length > 0 && proven(proveRule(r, [...this.chartLaws(), ...missed.map(x => x.law)]));
|
|
259
|
+
const allMissed = missed.length > 0 && proven(proveRule(r, [...this.chartLaws(), ...missed.map(x => x.law)], { search: false }));
|
|
260
260
|
for (const x of missed)
|
|
261
|
-
reached[x.name] = allMissed || proven(proveRule(r, [...this.chartLaws(), x.law])) ? 'unreachable' : 'unknown';
|
|
261
|
+
reached[x.name] = allMissed || proven(proveRule(r, [...this.chartLaws(), x.law], { search: false })) ? 'unreachable' : 'unknown';
|
|
262
262
|
// A way out is a transition to a state: an internal one stays where it is.
|
|
263
263
|
const stuck = regions.flatMap(reg => reg.leaves.filter(l => !reg.finals.has(l) && !placed.some(t => t.to !== undefined && t.region === reg.name && t.sources.includes(l))).map(l => label(reg, l)));
|
|
264
264
|
// Pairs as specific as each other, on one event, from a state they share: proven never both enabled, all at once,
|
|
@@ -270,9 +270,9 @@ export class Chart {
|
|
|
270
270
|
pairs.push({ t, u, law: r.stepLaw(`transitions ${t.order} and ${u.order} on ${t.event} are never both enabled`, (env) => all(env[`on${i}`], env[`on${j}`]).not()) });
|
|
271
271
|
}));
|
|
272
272
|
const ambiguous = [];
|
|
273
|
-
if (pairs.length && !proven(proveSteps(r, this.chartLaws(), pairs.map(x => x.law))))
|
|
273
|
+
if (pairs.length && !proven(proveSteps(r, this.chartLaws(), pairs.map(x => x.law), { search: false })))
|
|
274
274
|
for (const { t, u, law } of pairs) {
|
|
275
|
-
const v = proveSteps(r, this.chartLaws(), [law]);
|
|
275
|
+
const v = proveSteps(r, this.chartLaws(), [law], { search: false });
|
|
276
276
|
if (!proven(v))
|
|
277
277
|
ambiguous.push({ event: t.event, transitions: [t.order, u.order], verdict: v });
|
|
278
278
|
}
|
package/dist/core/discover.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type Discovery = {
|
|
|
12
12
|
rule: AnyRule;
|
|
13
13
|
found: Record<string, Found>;
|
|
14
14
|
};
|
|
15
|
-
export declare function discover(r: AnyRule, laws: Law[], stepLaws?: StepLaw[]): Discovery;
|
|
15
|
+
export declare function discover(r: AnyRule, laws: Law[], stepLaws?: StepLaw[], first?: Verdict): Discovery;
|
|
16
16
|
type TreeFold = {
|
|
17
17
|
fold: Fold;
|
|
18
18
|
lemmas?: Law[];
|
package/dist/core/discover.js
CHANGED
|
@@ -259,8 +259,8 @@ function houdiniList(r, part, cands, params) {
|
|
|
259
259
|
// --- the search ----------------------------------------------------------------------------------------------------
|
|
260
260
|
// The laws of r, and where they aren't proven as r states them, the lemmas found for its functions and lists: all that
|
|
261
261
|
// is proven first, then each dropped in turn while the laws stay proven without it.
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
// `first`: the laws' verdict on r alone, when the caller has it.
|
|
263
|
+
export function discover(r, laws, stepLaws = [], first = proveSteps(r, laws, stepLaws, { search: false })) {
|
|
264
264
|
if (first.proven && first.checked)
|
|
265
265
|
return { verdict: first, rule: r, found: {} };
|
|
266
266
|
const { params, fns, lists } = r.parts, ps = natParams(params);
|
|
@@ -284,7 +284,7 @@ export function discover(r, laws, stepLaws = []) {
|
|
|
284
284
|
listKept[part[0]] = out.kept;
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
|
-
const verdict = proveSteps(rule, laws, stepLaws);
|
|
287
|
+
const verdict = proveSteps(rule, laws, stepLaws, { search: false });
|
|
288
288
|
const found = () => ({
|
|
289
289
|
...Object.fromEntries(Object.entries(fnMade).map(([n, m]) => { const args = [...(recurOf(m.d).def.args ?? [])]; return [n, { ...(m.lemmas.length ? { lemmas: m.lemmas.map(c => recurSrc(c, args, false)) } : {}), ...(m.between.length ? { between: m.between.map(c => recurSrc(c, args, true)) } : {}) }]; })),
|
|
290
290
|
...Object.fromEntries(Object.entries(listKept).map(([n, cs]) => [n, { lemmas: cs.map(c => listSrc(c, Object.keys(lists.find(l => l[0] === n)[2]), ps, c.target)) }])),
|
|
@@ -297,7 +297,7 @@ export function discover(r, laws, stepLaws = []) {
|
|
|
297
297
|
// A rule made again and the laws proven of it; or the lemma whose proof failed without the one dropped.
|
|
298
298
|
const tryRule = (next) => {
|
|
299
299
|
try {
|
|
300
|
-
const r2 = next(), v = proveSteps(r2, laws, stepLaws);
|
|
300
|
+
const r2 = next(), v = proveSteps(r2, laws, stepLaws, { search: false });
|
|
301
301
|
return v.proven && v.checked ? { rule: r2, verdict: v } : undefined;
|
|
302
302
|
}
|
|
303
303
|
catch (e) {
|
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
|
};
|
|
@@ -473,6 +493,8 @@ type Spec = {
|
|
|
473
493
|
nodes?: [string, Kind<unknown>, string[], Record<string, NodeFold<unknown, string>>][];
|
|
474
494
|
kinded?: [string, Kind<unknown>][];
|
|
475
495
|
carried?: string[];
|
|
496
|
+
seqs?: string[];
|
|
497
|
+
seqTables?: string[];
|
|
476
498
|
lists2?: [string, Kind<unknown>, Record<string, ListFold<unknown>>, Record<string, ListLemma>][];
|
|
477
499
|
fns?: [string, FnDef][];
|
|
478
500
|
facts?: ((env: never) => Record<string, Cond | Forall>)[];
|
|
@@ -539,6 +561,12 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
539
561
|
carries<const N extends string>(...names: N[]): Rule<P, I & {
|
|
540
562
|
[k in N]: MapRef;
|
|
541
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>;
|
|
542
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 & {
|
|
543
571
|
[k in N]: CarriedList<V, Fd>;
|
|
544
572
|
}, S, M, F, R, Su, Q>;
|
|
@@ -604,7 +632,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
604
632
|
[k in keyof P]: Raw;
|
|
605
633
|
}): Record<string, number>;
|
|
606
634
|
encodeInput(values: {
|
|
607
|
-
[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;
|
|
608
636
|
}, params?: {
|
|
609
637
|
[k in keyof P]: Raw;
|
|
610
638
|
}): Request;
|
|
@@ -678,7 +706,7 @@ type Value<T> = {
|
|
|
678
706
|
[k in keyof T]: ValueOf<T[k]>;
|
|
679
707
|
};
|
|
680
708
|
type RawInputs<T> = {
|
|
681
|
-
[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]>;
|
|
682
710
|
};
|
|
683
711
|
type Field = Term | Cond | Int | Decimal | Choice<string> | Str | Variant<any, any> | Maybe<any> | {
|
|
684
712
|
[f: string]: Field | number;
|
|
@@ -770,13 +798,30 @@ export type Verdict = {
|
|
|
770
798
|
params: Record<string, number>;
|
|
771
799
|
trace: Record<string, unknown>[];
|
|
772
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
|
+
};
|
|
773
818
|
};
|
|
774
819
|
export declare class Unproven extends Error {
|
|
775
820
|
readonly verdict: Verdict;
|
|
776
821
|
constructor(what: string, verdict: Verdict);
|
|
777
822
|
}
|
|
778
|
-
export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[]): Verdict;
|
|
779
|
-
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;
|
|
780
825
|
export type { StepLaw };
|
|
781
826
|
export type { Liveness };
|
|
782
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,7 +1450,7 @@ 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 };
|
|
@@ -1631,6 +1670,16 @@ export class Rule {
|
|
|
1631
1670
|
facts(f) { return this.with({ facts: [...(this.spec.facts ?? []), f] }); }
|
|
1632
1671
|
// Maps each request carries whole (a set of names, say: cells 0 or 1), every cell past them 0; read like any map.
|
|
1633
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
|
+
}
|
|
1634
1683
|
// A list of values of a kind each request carries, with folds over it and lemmas about the folds (CarriedList):
|
|
1635
1684
|
// .carriesList('Lines', record({amount: nat}), {total: {from: 0, step: (acc, line) => acc.plus(line.amount)}})
|
|
1636
1685
|
// Each fold's start and step, and each lemma, may read the params declared before it.
|
|
@@ -1846,6 +1895,15 @@ export class Rule {
|
|
|
1846
1895
|
out[n] = k.value(n);
|
|
1847
1896
|
for (const n of this.spec.carried ?? [])
|
|
1848
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
|
+
}
|
|
1849
1907
|
const ps = Object.fromEntries(this.spec.params.map(([n]) => [n, out[n]]));
|
|
1850
1908
|
for (const [n, k, fs, ls] of this.spec.lists2 ?? []) {
|
|
1851
1909
|
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), ls, ps, this.spec.params);
|
|
@@ -2187,16 +2245,35 @@ export class Rule {
|
|
|
2187
2245
|
// --- running it -----------------------------------------------------------------------------------------------------
|
|
2188
2246
|
// Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
|
|
2189
2247
|
encodeParams(values) { return encode(this.spec.params, values); }
|
|
2190
|
-
// A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural
|
|
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.
|
|
2191
2250
|
// A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
|
|
2192
2251
|
// folds that read the params are computed with.
|
|
2193
2252
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2194
2253
|
encodeInput(values, params) {
|
|
2195
2254
|
const at = params && this.paramValues(this.encodeParams(params));
|
|
2196
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])]));
|
|
2197
|
-
|
|
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])) });
|
|
2198
2274
|
for (const l of Object.values(lists))
|
|
2199
2275
|
Object.assign(out, l.maps);
|
|
2276
|
+
Object.assign(out, seqMaps);
|
|
2200
2277
|
for (const n of this.spec.carried ?? []) {
|
|
2201
2278
|
if (n in out)
|
|
2202
2279
|
continue;
|
|
@@ -2432,14 +2509,16 @@ const withKinds = (r, laws) => [...r.kindLaws().filter(k => !laws.some(l => l.na
|
|
|
2432
2509
|
// interpreter took. A rule that reads functions is skipped: the kernel knows them only by their facts (a recurrence's
|
|
2433
2510
|
// induction machine reads the function it proves things about), so a run computing them proves nothing about the goal.
|
|
2434
2511
|
const refuteBudget = { trials: 60, depth: 6, bound: 3, ms: 150 };
|
|
2435
|
-
|
|
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) {
|
|
2436
2515
|
const m = r.machine;
|
|
2437
2516
|
if (Object.keys(m.fns ?? {}).length || m.facts?.length)
|
|
2438
2517
|
return undefined;
|
|
2439
|
-
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;
|
|
2440
2519
|
const shown = (input) => Object.fromEntries(Object.entries(input).map(([n, x]) => [n, typeof x === 'number' ? x : Object.fromEntries(x)]));
|
|
2441
2520
|
try {
|
|
2442
|
-
for (let trial = 0; trial <
|
|
2521
|
+
for (let trial = 0; trial < budget.trials && performance.now() < until; trial++) {
|
|
2443
2522
|
const params = r.randomParams(rand, bound);
|
|
2444
2523
|
if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
|
|
2445
2524
|
continue;
|
|
@@ -2451,7 +2530,7 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2451
2530
|
for (const { w, on } of watching)
|
|
2452
2531
|
if (!('then' in w) && !on.start(st))
|
|
2453
2532
|
return broken(w, '@start', st);
|
|
2454
|
-
for (let d = 0, tries = 0; d <
|
|
2533
|
+
for (let d = 0, tries = 0; d < budget.depth && tries < 40; tries++) {
|
|
2455
2534
|
const input = r.randomInput(rand, bound, params);
|
|
2456
2535
|
if (!allowed(m, run, params, st, input))
|
|
2457
2536
|
continue;
|
|
@@ -2471,7 +2550,7 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2471
2550
|
// variable of a kind is drawn as its kind draws (33 yes/nos drawn from 0..7 almost never make a lawful state).
|
|
2472
2551
|
const steps1 = stepLaws.filter(sl => (sl.steps ?? 1) === 1), given = withKinds(r, laws);
|
|
2473
2552
|
const wide = Object.keys(m.state).length + m.inputs.length >= 32, lawsHere = wide ? given : [], kinded = wide ? r.kindsOfState() : [];
|
|
2474
|
-
for (let trial = 0; !m.history && (steps1.length || lawsHere.length) && trial <
|
|
2553
|
+
for (let trial = 0; !m.history && (steps1.length || lawsHere.length) && trial < budget.trials * 4 && performance.now() < until; trial++) {
|
|
2475
2554
|
const params = r.randomParams(rand, bound);
|
|
2476
2555
|
if (m.pre && !evalC(m.pre, params, { scalars: {}, maps: {} }, m))
|
|
2477
2556
|
continue;
|
|
@@ -2499,25 +2578,56 @@ function refuteByRuns(r, laws, stepLaws = []) {
|
|
|
2499
2578
|
return undefined;
|
|
2500
2579
|
}
|
|
2501
2580
|
const refutedVerdict = (out) => ({ proven: false, checked: false, goal: out.goal, stuck: 'sat', witness: out.witness, ...(out.run ? { run: out.run } : {}) });
|
|
2502
|
-
export function proveRule(r, laws) {
|
|
2581
|
+
export function proveRule(r, laws, opts = {}) {
|
|
2503
2582
|
const refuted = refuteByRuns(r, laws);
|
|
2504
2583
|
if (refuted)
|
|
2505
2584
|
return refutedVerdict(refuted);
|
|
2506
2585
|
const out = proveLaws(r.machine, withKinds(r, laws));
|
|
2507
|
-
|
|
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);
|
|
2508
2588
|
}
|
|
2509
|
-
export function proveSteps(r, laws, stepLaws) {
|
|
2589
|
+
export function proveSteps(r, laws, stepLaws, opts = {}) {
|
|
2510
2590
|
const refuted = refuteByRuns(r, laws, stepLaws);
|
|
2511
2591
|
if (refuted)
|
|
2512
2592
|
return refutedVerdict(refuted);
|
|
2513
2593
|
const out = proveStepLaws(r.machine, withKinds(r, laws), stepLaws);
|
|
2514
|
-
|
|
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 } : {}) } };
|
|
2515
2625
|
}
|
|
2516
2626
|
export function proveLeadsTo(r, laws, l) {
|
|
2517
2627
|
const out = proveLiveness(r.machine, withKinds(r, laws), l);
|
|
2518
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 } : {}) } : {}) };
|
|
2519
2629
|
}
|
|
2520
|
-
export const proveDecision = (d) => (d.steps ? proveSteps(d.steps.rule, [], [d.steps.law]) : goalsVerdict([d.goal]));
|
|
2630
|
+
export const proveDecision = (d) => (d.steps ? proveSteps(d.steps.rule, [], [d.steps.law], { search: false }) : goalsVerdict([d.goal]));
|
|
2521
2631
|
const seeded = (seed) => { let x = seed; return k => { x = (x * 1103515245 + 12345) % 2 ** 31; return Math.floor(x / 2 ** 31 * k); }; };
|
|
2522
2632
|
// A decision's callbacks against its condition, on random params and requests: `given` and `then` each run with every
|
|
2523
2633
|
// value known, against what the interpreter evaluates of the decision built from them.
|
package/dist/core/infer.js
CHANGED
|
@@ -112,7 +112,7 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
112
112
|
const prove = (ls) => {
|
|
113
113
|
proofs++;
|
|
114
114
|
if (rule)
|
|
115
|
-
return stepLaws.length ? proveSteps(rule, ls, stepLaws) : proveRule(rule, ls);
|
|
115
|
+
return stepLaws.length ? proveSteps(rule, ls, stepLaws, { search: false }) : proveRule(rule, ls, { search: false });
|
|
116
116
|
const out = stepLaws.length ? proveStepLaws(m, ls, stepLaws) : proveLaws(m, ls);
|
|
117
117
|
return { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
|
|
118
118
|
};
|
|
@@ -245,16 +245,18 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
245
245
|
};
|
|
246
246
|
const choose = (k, of) => { let r = 1; for (let i = 0; i < k; i++)
|
|
247
247
|
r = r * (of - i) / (i + 1); return r; };
|
|
248
|
-
|
|
248
|
+
// A choice's index orders its values arbitrarily: it is a guard, or alone (a bound on it reads as the values it
|
|
249
|
+
// allows), never a term among others.
|
|
250
|
+
const plain = atoms.flatMap((a, i) => (a.cell ? [] : [i])), terms = plain.filter(i => !modeOf(i)?.choice), P = terms.length;
|
|
249
251
|
const scalarOk = (acc) => acc.some(([i]) => !atoms[i].param);
|
|
250
252
|
pick(1, [1], plain, scalarOk, false);
|
|
251
|
-
pick(2, choose(2, P) * 4 * mags.length ** 2 <= maxCandidates / 4 ? mags : [1],
|
|
253
|
+
pick(2, choose(2, P) * 4 * mags.length ** 2 <= maxCandidates / 4 ? mags : [1], terms, scalarOk, false);
|
|
252
254
|
if (choose(3, P) * 8 * mags.length ** 3 <= maxCandidates / 2)
|
|
253
|
-
pick(3, mags,
|
|
255
|
+
pick(3, mags, terms, scalarOk, false);
|
|
254
256
|
else if (choose(3, P) * 8 <= maxCandidates / 2)
|
|
255
|
-
pick(3, [1],
|
|
257
|
+
pick(3, [1], terms, scalarOk, false);
|
|
256
258
|
if (cellAtoms.length) {
|
|
257
|
-
const all = atoms.
|
|
259
|
+
const all = atoms.flatMap((_, i) => (modeOf(i)?.choice ? [] : [i])), cellOk = (acc) => acc.some(([i]) => atoms[i].cell) && acc.filter(([i]) => !atoms[i].cell).length <= 1;
|
|
258
260
|
pick(1, [1], cellAtoms, () => true, true);
|
|
259
261
|
pick(2, [1], all, cellOk, true);
|
|
260
262
|
if (choose(3, A) * 8 <= maxCandidates / 4)
|
|
@@ -329,7 +331,8 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
329
331
|
return `${nameOf(f.unreachable.atom)} is never ${shownValue(f.unreachable.atom, f.unreachable.value)}`;
|
|
330
332
|
const { l, r } = sides(f), within = allowedValues(f);
|
|
331
333
|
const body = within ? `${nameOf(f.coef[0][0])} is ${within.map(v => shownValue(f.coef[0][0], v)).join(' or ')}` : `${l} <= ${r}`;
|
|
332
|
-
const
|
|
334
|
+
const under = f.when ? (({ l: wl, r: wr }) => `${wl} <= ${wr} → `)(sides({ coef: f.when.coef, d: f.when.d, cells: false, size: 0 })) : '';
|
|
335
|
+
const guarded = !f.guard ? under + body : 'keyNot' in f.guard ? `k = ${nameOf(f.guard.keyNot)} or ${body}` : `${nameOf(f.guard.atom)} is ${shownValue(f.guard.atom, f.guard.value)} → ${under}${body}`;
|
|
333
336
|
return f.cells ? `every k: ${guarded}` : guarded;
|
|
334
337
|
};
|
|
335
338
|
const sum = (es) => es.reduce((p, q) => plus(p, q), n(0));
|
|
@@ -347,12 +350,13 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
347
350
|
if (f.unreachable)
|
|
348
351
|
return notAt(f.unreachable.atom, f.unreachable.value);
|
|
349
352
|
const lhs = sum([...partsOf(f, 1), ...(f.d < 0 ? [n(-f.d)] : [])]), rhs = sum([...partsOf(f, -1), ...(f.d > 0 ? [n(f.d)] : [])]);
|
|
350
|
-
|
|
353
|
+
// Under a condition Σ ≤ d: its negation, −Σ ≤ −d − 1, or the body.
|
|
354
|
+
const body = f.when ? any(condOf({ coef: f.when.coef.map(([i, c]) => [i, -c]), d: -f.when.d - 1, cells: false, size: 0 }), atMost(lhs, rhs)) : atMost(lhs, rhs);
|
|
351
355
|
return !f.guard ? body : 'keyNot' in f.guard ? any(same(x(KEY), x(atoms[f.guard.keyNot].parts[0][0])), body) : any(notAt(f.guard.atom, f.guard.value), body);
|
|
352
356
|
};
|
|
353
357
|
// The builder call, for a rule whose atoms are its own variables (naturals, or integers alone).
|
|
354
358
|
const sourceOf = (f, text) => {
|
|
355
|
-
if (!rule)
|
|
359
|
+
if (!rule || f.when)
|
|
356
360
|
return undefined;
|
|
357
361
|
const used = [...new Set([...f.coef.map(([i]) => i), ...(f.guard ? ['keyNot' in f.guard ? f.guard.keyNot : f.guard.atom] : []), ...(f.unreachable ? [f.unreachable.atom] : [])])];
|
|
358
362
|
const ints = f.coef.filter(([i]) => atoms[i].int).length;
|
|
@@ -388,6 +392,13 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
388
392
|
else if (valueOf(atoms[f.guard.atom], env) !== f.guard.value)
|
|
389
393
|
return true;
|
|
390
394
|
}
|
|
395
|
+
if (f.when) {
|
|
396
|
+
let w = 0;
|
|
397
|
+
for (const [i, c] of f.when.coef)
|
|
398
|
+
w += c * valueOf(atoms[i], env);
|
|
399
|
+
if (w > f.when.d)
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
391
402
|
let s = 0;
|
|
392
403
|
for (const [i, c] of f.coef)
|
|
393
404
|
s += c * valueOf(atoms[i], env);
|
|
@@ -489,6 +500,97 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
489
500
|
}
|
|
490
501
|
return pickF;
|
|
491
502
|
};
|
|
503
|
+
// A counterexample to induction no candidate rules out: forms false at it that every sample meets under a condition
|
|
504
|
+
// it meets too (one value, or a sum or difference of two, at most a threshold: 0, ±1, or its own value there), the
|
|
505
|
+
// choice (loc, say) at its value there as the guard. A condition no sample meets gives its negation instead. Only the
|
|
506
|
+
// eight values most unusual at it are read (furthest past the samples in its guard, then the rarest there).
|
|
507
|
+
const choiceMode = modes.find(mo => mo.choice && !atoms[mo.atom].cell);
|
|
508
|
+
const condAtoms = plain.filter(i => !atoms[i].param && i !== choiceMode?.atom);
|
|
509
|
+
const fromCti = (p) => {
|
|
510
|
+
const stop = Math.min(until, performance.now() + Math.max(300, (until - performance.now()) / 8));
|
|
511
|
+
const sc = p.scalar, pv = (coef) => { let v = 0; for (const [i, c] of coef)
|
|
512
|
+
v += c * valueOf(atoms[i], sc); return v; };
|
|
513
|
+
const gv = choiceMode ? valueOf(atoms[choiceMode.atom], sc) : undefined;
|
|
514
|
+
const inGuard = [];
|
|
515
|
+
for (let j = 0; j < NS; j++)
|
|
516
|
+
if (gv === undefined || valsS[choiceMode.atom][j] === gv)
|
|
517
|
+
inGuard.push(j);
|
|
518
|
+
const unusual = (i) => {
|
|
519
|
+
const v = valueOf(atoms[i], sc);
|
|
520
|
+
let lo = Infinity, hi = -Infinity, same = 0;
|
|
521
|
+
for (const j of inGuard) {
|
|
522
|
+
const w = valsS[i][j];
|
|
523
|
+
if (w < lo)
|
|
524
|
+
lo = w;
|
|
525
|
+
if (w > hi)
|
|
526
|
+
hi = w;
|
|
527
|
+
if (w === v)
|
|
528
|
+
same++;
|
|
529
|
+
}
|
|
530
|
+
return [Math.max(0, lo - v, v - hi), -same];
|
|
531
|
+
};
|
|
532
|
+
const focus = condAtoms.map(i => ({ i, u: unusual(i) })).sort((a, b) => b.u[0] - a.u[0] || b.u[1] - a.u[1]).slice(0, 8).map(x => x.i);
|
|
533
|
+
// Over the focus: one or two values with the magnitudes, three with ±1.
|
|
534
|
+
const bodies = [];
|
|
535
|
+
const signed = (ks) => ks.flatMap(k => [k, -k]);
|
|
536
|
+
for (const [a, i] of focus.entries()) {
|
|
537
|
+
bodies.push({ coef: [[i, 1]] }, { coef: [[i, -1]] });
|
|
538
|
+
for (const [b2, k] of focus.slice(a + 1).entries()) {
|
|
539
|
+
for (const c1 of signed(mags))
|
|
540
|
+
for (const c2 of signed(mags))
|
|
541
|
+
if (gcd(c1, c2) === 1)
|
|
542
|
+
bodies.push({ coef: [[i, c1], [k, c2]] });
|
|
543
|
+
for (const q of focus.slice(a + b2 + 2))
|
|
544
|
+
for (const c1 of [1, -1])
|
|
545
|
+
for (const c2 of [1, -1])
|
|
546
|
+
for (const c3 of [1, -1])
|
|
547
|
+
bodies.push({ coef: [[i, c1], [k, c2], [q, c3]] });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const conds = [];
|
|
551
|
+
for (const [a, i] of focus.entries()) {
|
|
552
|
+
conds.push([[i, 1]], [[i, -1]]);
|
|
553
|
+
for (const k of focus.slice(a + 1))
|
|
554
|
+
conds.push([[i, 1], [k, -1]], [[i, -1], [k, 1]], [[i, 1], [k, 1]]);
|
|
555
|
+
}
|
|
556
|
+
const out = [], guard = choiceMode ? { atom: choiceMode.atom, value: gv } : undefined;
|
|
557
|
+
for (const coef of conds) {
|
|
558
|
+
const here = pv(coef);
|
|
559
|
+
for (const d of [...new Set([here, 0, 1, -1])].filter(d => here <= d)) {
|
|
560
|
+
if (performance.now() > stop)
|
|
561
|
+
break;
|
|
562
|
+
const rows = [];
|
|
563
|
+
for (const j of inGuard)
|
|
564
|
+
if (at(coef, valsS, j) <= d)
|
|
565
|
+
rows.push(j);
|
|
566
|
+
const when = { coef, d }, base = sizeOf(coef) + 80;
|
|
567
|
+
if (!rows.length) {
|
|
568
|
+
out.push({ coef: coef.map(([i, c]) => [i, -c]), d: -d - 1, cells: false, ...(guard ? { guard } : {}), size: base });
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const stride = Math.max(1, Math.ceil(rows.length / 600));
|
|
572
|
+
for (const [q, sh] of bodies.entries()) {
|
|
573
|
+
if (q % 256 === 0 && performance.now() > stop)
|
|
574
|
+
break;
|
|
575
|
+
const v = pv(sh.coef);
|
|
576
|
+
let most = -Infinity, ok = true;
|
|
577
|
+
for (let t = 0; t < rows.length; t += stride) {
|
|
578
|
+
const w = at(sh.coef, valsS, rows[t]);
|
|
579
|
+
if (w >= v) {
|
|
580
|
+
ok = false;
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
if (w > most)
|
|
584
|
+
most = w;
|
|
585
|
+
}
|
|
586
|
+
if (ok && Number.isFinite(most))
|
|
587
|
+
out.push({ coef: sh.coef, d: most, cells: false, ...(guard ? { guard } : {}), when, size: base + sizeOf(sh.coef) });
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
// A stride may skip a sample that breaks a form: each is checked again at every sample in its guard.
|
|
592
|
+
return out.sort((x, y) => x.size - y.size).slice(0, 2000).filter(f => inGuard.every(j => holdsRow(f, rowsS[j]))).slice(0, 400);
|
|
593
|
+
};
|
|
492
594
|
// The search proves one law at a time (proveLaw), each with every law assumed, and keeps what it proved while laws
|
|
493
595
|
// are only added (more laws assumed keep a proof a proof); dropping one forgets it all. Once every law is proven so,
|
|
494
596
|
// the set is proven as a whole, by the rule's own prover: that verdict is the answer.
|
|
@@ -545,7 +647,13 @@ export function inferLaws(target, laws, stepLaws = [], opts = {}) {
|
|
|
545
647
|
const p = /@step|@\d+ steps/.test(last.goal) && last.witness ? witnessPoint(last.witness) : undefined;
|
|
546
648
|
if (p)
|
|
547
649
|
ctis.push(p);
|
|
548
|
-
|
|
650
|
+
let next = p ? best(p) : undefined;
|
|
651
|
+
if (!next && p) {
|
|
652
|
+
const t = performance.now(), made = fromCti(p);
|
|
653
|
+
pool.push(...made);
|
|
654
|
+
next = best(p);
|
|
655
|
+
opts.progress?.('cti', { forms: made.length, ms: Math.round(performance.now() - t) });
|
|
656
|
+
}
|
|
549
657
|
if (next) {
|
|
550
658
|
chosen.push(next);
|
|
551
659
|
continue;
|
package/dist/kernel/prove.js
CHANGED
|
@@ -283,6 +283,27 @@ function growPlain(goal, path, budget) {
|
|
|
283
283
|
return { tree: leafOf(third.steps, weighted, squares), nodes: budget.nodes };
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
|
+
// A multiplier: every row times a factor the path has at least 1 (the row 1 − x <= 0), with the squares (x·a − b)
|
|
287
|
+
// and (x·b − a) of each product a·b the rows read: a certificate that multiplies the goal by x first ((Σv)² <= n·Σv²
|
|
288
|
+
// over running samples is n times the goal, closed by (n·v − s)²).
|
|
289
|
+
const ones = rows.flatMap((r, i) => { const ks = Object.keys(r.lin); return ks.length === 2 && r.lin['1'] === 1 && ks.some(key => key !== '1' && r.lin[key] === -1 && factorSet.has(key)) ? [i] : []; });
|
|
290
|
+
for (const xi of ones.slice(0, 3)) {
|
|
291
|
+
const x = Object.keys(rows[xi].lin).find(key => key !== '1'), name = (fs) => [...fs].sort().join('*');
|
|
292
|
+
const forms = new Map();
|
|
293
|
+
for (const m of new Set(rows.flatMap(r => Object.keys(r.lin).filter(isMonomial)))) {
|
|
294
|
+
const [a, b, ...more] = factorsOf(m);
|
|
295
|
+
if (!a || !b || more.length)
|
|
296
|
+
continue;
|
|
297
|
+
for (const [u, w] of [[a, b], [b, a]]) {
|
|
298
|
+
const t = { [name([x, u])]: 1, [w]: -1 };
|
|
299
|
+
forms.set(canon(t), t);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const times = relevant.map(i => [i, xi]), sq = [...forms.values()].slice(0, 32);
|
|
303
|
+
const multiplied = solve(withProducts(rows, times, sq));
|
|
304
|
+
if (multiplied.kind === 'refuted')
|
|
305
|
+
return { tree: leafOf(multiplied.steps, times, sq), nodes: budget.nodes };
|
|
306
|
+
}
|
|
286
307
|
const factors = [...new Set(rows.flatMap(r => Object.keys(r.lin).filter(isMonomial).flatMap(factorsOf)))];
|
|
287
308
|
const fresh = (lin) => !path.has(canon(lin)) && !path.has(canonFlip(lin));
|
|
288
309
|
// Split a factor at zero, else two factors against each other (so (x - y)² >= 0 becomes a product of rows).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdp-wired",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "An automatic prover for stateful rules (budgets, caps, windows, workflows, policies): laws proven with no proof to write, checked again in Lean, counterexamples found on the GPU, the rule shipped as code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"verification",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"build": "bun scripts/build.ts",
|
|
102
102
|
"prepublishOnly": "bun scripts/build.ts",
|
|
103
103
|
"smoke": "sh scripts/smoke.sh",
|
|
104
|
-
"test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts gdp.test.ts datatypes.test.ts lemmas.test.ts discover.test.ts infer.test.ts invoices.test.ts folds.test.ts trees.test.ts pairs.test.ts chart.test.ts xstate.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/guide.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/datatypes.ts && bun examples/lemmas.ts && bun examples/discover.ts && bun examples/invoices.ts && bun examples/folds.ts && bun examples/trees.ts && bun examples/pairs.ts && bun examples/groups.ts && bun examples/charts.ts && bun examples/xstate.ts && bun examples/infer.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/gdp.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
|
|
104
|
+
"test": "bun scripts/gen-lean-library.ts --check && cd core && bun test solver.test.ts dsl.test.ts conformance.test.ts compose.test.ts quantifiers.test.ts functions.test.ts keyterms.test.ts lists.test.ts liveness.test.ts sizes.test.ts values.test.ts builders.test.ts gdp.test.ts datatypes.test.ts lemmas.test.ts discover.test.ts infer.test.ts seqs.test.ts search.test.ts invoices.test.ts folds.test.ts trees.test.ts pairs.test.ts chart.test.ts xstate.test.ts && cd ../kernel && bun test tamper.test.ts refusals.test.ts steplaw.test.ts sets.test.ts && cd ../examples && bun test examples.test.ts && cd .. && bun examples/readme.ts && bun examples/guide.ts && bun examples/composition.ts && bun examples/sets.ts && bun examples/quantifiers.ts && bun examples/functions.ts && bun examples/datatypes.ts && bun examples/lemmas.ts && bun examples/discover.ts && bun examples/invoices.ts && bun examples/folds.ts && bun examples/trees.ts && bun examples/pairs.ts && bun examples/groups.ts && bun examples/charts.ts && bun examples/xstate.ts && bun examples/infer.ts && bun examples/scopes.ts && bun examples/lists.ts && bun examples/sizes.ts && bun examples/liveness.ts && bun examples/budget.ts && bun examples/gdp.ts && bun examples/steps.ts && bun examples/jev-fabric/run.ts"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
107
|
"@gdp-ts/core": "0.1.0",
|