gdp-wired 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -6
- package/dist/core/cycles.d.ts +12 -0
- package/dist/core/cycles.js +7 -0
- package/dist/core/data.d.ts +138 -13
- package/dist/core/data.js +842 -145
- package/dist/core/dsl.d.ts +27 -1
- package/dist/core/dsl.js +137 -26
- package/dist/core/exists.d.ts +26 -0
- package/dist/core/exists.js +625 -0
- package/dist/core/infer.d.ts +13 -0
- package/dist/core/infer.js +258 -41
- package/dist/core/machine.d.ts +27 -0
- package/dist/core/machine.js +45 -0
- package/dist/core/transfer.d.ts +17 -0
- package/dist/core/transfer.js +468 -0
- package/dist/core/watch.js +5 -1
- package/dist/kernel/prove.d.ts +5 -1
- package/dist/kernel/prove.js +11 -5
- package/dist/lean-kernel/emit.d.ts +2 -0
- package/dist/lean-kernel/emit.js +196 -15
- package/dist/lean-kernel/library.js +2 -2
- package/package.json +2 -2
package/dist/core/dsl.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export declare class Term {
|
|
|
10
10
|
minus(b: TermLike): Term;
|
|
11
11
|
times(k: number): Term;
|
|
12
12
|
mul(b: TermLike): Term;
|
|
13
|
+
modBy(k: number): Term;
|
|
14
|
+
divBy(k: number): Term;
|
|
13
15
|
lt(b: TermLike): Cond;
|
|
14
16
|
atMost(b: TermLike): Cond;
|
|
15
17
|
gt(b: TermLike): Cond;
|
|
@@ -20,7 +22,8 @@ export declare class Term {
|
|
|
20
22
|
export declare class Cond {
|
|
21
23
|
readonly c: C;
|
|
22
24
|
readonly ok?: boolean | undefined;
|
|
23
|
-
|
|
25
|
+
readonly ex?: readonly Bound[] | undefined;
|
|
26
|
+
constructor(c: C, ok?: boolean | undefined, ex?: readonly Bound[] | undefined);
|
|
24
27
|
and(...xs: Cond[]): Cond;
|
|
25
28
|
or(...xs: Cond[]): Cond;
|
|
26
29
|
not(): Cond;
|
|
@@ -94,6 +97,8 @@ export declare class Int {
|
|
|
94
97
|
min(b: IntLike): Int;
|
|
95
98
|
max(b: IntLike): Int;
|
|
96
99
|
get isNegative(): Cond;
|
|
100
|
+
modBy(k: number): Term;
|
|
101
|
+
divBy(k: number): Int;
|
|
97
102
|
get abs(): Term;
|
|
98
103
|
get clamped(): Term;
|
|
99
104
|
}
|
|
@@ -261,6 +266,20 @@ export type MapWrite = {
|
|
|
261
266
|
};
|
|
262
267
|
export declare const everyKey: (f: (key: Term, key2: Term, key3: Term) => Cond) => Cond;
|
|
263
268
|
export declare const someKey: (f: (key: Term) => Cond, witness?: TermLike) => Cond;
|
|
269
|
+
export type ExistsWitness<V> = V | number | {
|
|
270
|
+
start: V | number;
|
|
271
|
+
step: (before: V, env: never) => V | number;
|
|
272
|
+
};
|
|
273
|
+
type Bound = {
|
|
274
|
+
vars: string[];
|
|
275
|
+
body: C;
|
|
276
|
+
pos: boolean;
|
|
277
|
+
terms?: Record<string, E>;
|
|
278
|
+
start?: Record<string, E>;
|
|
279
|
+
step?: (env: Record<string, unknown>) => Record<string, E>;
|
|
280
|
+
};
|
|
281
|
+
export declare function exists(f: (x: Term) => Cond, witness?: ExistsWitness<Term>): Cond;
|
|
282
|
+
export declare function exists<V>(kind: Kind<V>, f: (x: V) => Cond, witness?: ExistsWitness<V>): Cond;
|
|
264
283
|
export declare class MapRef {
|
|
265
284
|
readonly name: string;
|
|
266
285
|
readonly init: number;
|
|
@@ -498,6 +517,7 @@ type Spec = {
|
|
|
498
517
|
lists2?: [string, Kind<unknown>, Record<string, ListFold<unknown>>, Record<string, ListLemma>][];
|
|
499
518
|
fns?: [string, FnDef][];
|
|
500
519
|
facts?: ((env: never) => Record<string, Cond | Forall>)[];
|
|
520
|
+
arith?: Set<number>;
|
|
501
521
|
flags: ((env: never) => Record<string, Cond>)[];
|
|
502
522
|
step?: (env: never) => Record<string, TermLike | MapWrite | undefined>;
|
|
503
523
|
pre: ((p: never) => Cond)[];
|
|
@@ -588,6 +608,10 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
588
608
|
prune(order: keyof R & string, after: (p: P) => TermLike): Rule<P, I, S, M, F, R, Su, Q>;
|
|
589
609
|
law(name: string, f: (env: P & S & M & Su) => Cond | Forall): Law;
|
|
590
610
|
private lawOf;
|
|
611
|
+
private someOf;
|
|
612
|
+
private divides;
|
|
613
|
+
dividesBy(...ks: number[]): this;
|
|
614
|
+
withDivisors(...ks: number[]): this;
|
|
591
615
|
kindLaws(): Law[];
|
|
592
616
|
stepLaw(name: string, then: (before: P & I & S & M & F & Su & Q, after: S & M & Su) => Cond | Forall, when?: (before: P & I & S & M & F & Su & Q) => Cond): StepLaw;
|
|
593
617
|
stepsLaw(name: string, k: number, then: (first: P & I & S & M & F & Su & Q, last: S & M & Su, requests: (I & Q)[]) => Cond | Forall, when?: (before: P & I & S & M & F & Su & Q, j: number) => Cond | undefined): StepLaw;
|
|
@@ -631,6 +655,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
|
|
|
631
655
|
encodeParams(values: {
|
|
632
656
|
[k in keyof P]: Raw;
|
|
633
657
|
}): Record<string, number>;
|
|
658
|
+
private withSkolems;
|
|
634
659
|
encodeInput(values: {
|
|
635
660
|
[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;
|
|
636
661
|
}, params?: {
|
|
@@ -800,6 +825,7 @@ export type Verdict = {
|
|
|
800
825
|
trace: Record<string, unknown>[];
|
|
801
826
|
};
|
|
802
827
|
found?: Found;
|
|
828
|
+
witnesses?: Record<string, string>;
|
|
803
829
|
};
|
|
804
830
|
export type Found = {
|
|
805
831
|
laws?: {
|
package/dist/core/dsl.js
CHANGED
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
//
|
|
16
16
|
// A step returns its updates as one object: every right-hand side reads the state before the step, as the kernel and
|
|
17
17
|
// both runtimes do.
|
|
18
|
-
import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf, pairedArgs } from "./machine.js";
|
|
18
|
+
import { carriedView, compileFunction, divName, divisor, divisorsIn, evalC, evalE, interpret, isArithmetic, loadJs, modName, numbersOf, pairedArgs, withArithmetic } from "./machine.js";
|
|
19
19
|
import { allowed, keysOf, requestEnv, watch } from "./watch.js";
|
|
20
|
+
import { someHolds, substVars } from "./exists.js";
|
|
20
21
|
import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
|
|
21
22
|
import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws, usualProof, withDeadline } from "../kernel/prove.js";
|
|
22
23
|
import { certCyclic, certOn, crossCyclic, cyclicOn, foldNodes, proveCyclic, quietly, recurNodes, translatedProof, uncovered } from "./cyclic.js";
|
|
@@ -53,6 +54,11 @@ export class Term {
|
|
|
53
54
|
minus(b) { return new Term({ op: 'sub', a: this.e, b: termOf(b) }, both(this.val, valOf(b), (x, y) => Math.max(0, x - y))); }
|
|
54
55
|
times(k) { return new Term({ op: 'mul', k: naturalConst(k), a: this.e }, both(this.val, k, (x, y) => x * y)); }
|
|
55
56
|
mul(b) { return new Term({ op: 'prod', a: this.e, b: termOf(b) }, both(this.val, valOf(b), (x, y) => x * y)); }
|
|
57
|
+
// x div k and x mod k, rounded down, for a constant k >= 1: the rule's functions div$k and mod$k at x, which the
|
|
58
|
+
// kernel knows by x = k·(x div k) + x mod k and x mod k < k (machine.ts's arithmetic). Division by a term that isn't a
|
|
59
|
+
// constant has no such fact in linear arithmetic (x = y·q + r is a product of two unknowns), so it isn't offered.
|
|
60
|
+
modBy(k) { divisor(k, 'modBy'); return new Term({ op: 'get', map: modName(k), key: this.e }, this.val === undefined ? undefined : this.val % k); }
|
|
61
|
+
divBy(k) { divisor(k, 'divBy'); return new Term({ op: 'get', map: divName(k), key: this.e }, this.val === undefined ? undefined : Math.floor(this.val / k)); }
|
|
56
62
|
lt(b) { return new Cond({ op: '<', a: this.e, b: termOf(b) }, both(this.val, valOf(b), (x, y) => x < y)); }
|
|
57
63
|
atMost(b) { return new Cond({ op: '<=', a: this.e, b: termOf(b) }, both(this.val, valOf(b), (x, y) => x <= y)); }
|
|
58
64
|
gt(b) { return new Cond({ op: '<', a: termOf(b), b: this.e }, both(this.val, valOf(b), (x, y) => x > y)); }
|
|
@@ -67,21 +73,26 @@ const known = (xs) => (xs.every(x => x !== undefined) ? xs : undefined);
|
|
|
67
73
|
export class Cond {
|
|
68
74
|
c;
|
|
69
75
|
ok;
|
|
70
|
-
|
|
76
|
+
ex;
|
|
77
|
+
// `ex`: the existentials (exists) inside, each with whether it reads as "there exists" where it sits (under no not, or
|
|
78
|
+
// an even number of them): a law, the assumption or a precondition pulls each such one out as ∃ before the whole.
|
|
79
|
+
constructor(c, ok, ex) {
|
|
71
80
|
this.c = c;
|
|
72
81
|
this.ok = ok;
|
|
82
|
+
this.ex = ex;
|
|
73
83
|
}
|
|
74
84
|
and(...xs) { return all(this, ...xs); }
|
|
75
85
|
or(...xs) { return any(this, ...xs); }
|
|
76
|
-
not() { return new Cond({ op: 'not', x: this.c }, this.ok === undefined ? undefined : !this.ok); }
|
|
86
|
+
not() { return new Cond({ op: 'not', x: this.c }, this.ok === undefined ? undefined : !this.ok, this.ex?.map(b => ({ ...b, pos: !b.pos }))); }
|
|
77
87
|
implies(b) { return any(this.not(), b); }
|
|
78
88
|
iff(b) { return all(this.implies(b), b.implies(this)); }
|
|
79
89
|
}
|
|
80
90
|
export const lit = (v) => new Term(termOf(v), v);
|
|
81
91
|
export const yes = new Cond({ op: '<=', a: { op: 'const', value: 0 }, b: { op: 'const', value: 0 } }, true);
|
|
82
92
|
export const no = new Cond({ op: '<', a: { op: 'const', value: 0 }, b: { op: 'const', value: 0 } }, false);
|
|
83
|
-
|
|
84
|
-
export const
|
|
93
|
+
const exOf = (cs) => (cs.some(c => c.ex?.length) ? cs.flatMap(c => c.ex ?? []) : undefined);
|
|
94
|
+
export const all = (...cs) => (cs.length === 0 ? yes : cs.length === 1 ? cs[0] : new Cond({ op: 'and', xs: cs.map(c => c.c) }, known(cs.map(c => c.ok))?.every(x => x), exOf(cs)));
|
|
95
|
+
export const any = (...cs) => (cs.length === 0 ? no : cs.length === 1 ? cs[0] : new Cond({ op: 'or', xs: cs.map(c => c.c) }, known(cs.map(c => c.ok))?.some(x => x), exOf(cs)));
|
|
85
96
|
export const not = (c) => c.not();
|
|
86
97
|
export const min = (a, b) => new Term({ op: 'min', a: termOf(a), b: termOf(b) }, both(valOf(a), valOf(b), Math.min));
|
|
87
98
|
export const max = (a, b) => new Term({ op: 'max', a: termOf(a), b: termOf(b) }, both(valOf(a), valOf(b), Math.max));
|
|
@@ -244,6 +255,11 @@ export class Int {
|
|
|
244
255
|
min(b) { return when(this.atMost(b), this, b); }
|
|
245
256
|
max(b) { return when(this.atLeast(b), this, b); }
|
|
246
257
|
get isNegative() { return this.neg.gt(this.pos); }
|
|
258
|
+
// x mod k and x div k for a constant k >= 1, as SMT-LIB and Lean's Int read them for k > 0: x = k·(x div k) + x mod k
|
|
259
|
+
// with 0 <= x mod k < k (so -7 div 2 is -4 and -7 mod 2 is 1). Over the naturals: pos + (k − 1)·neg is x + k·neg,
|
|
260
|
+
// the same remainder, and its quotient is x div k + neg, so both are the natural division of that one natural.
|
|
261
|
+
modBy(k) { divisor(k, 'modBy'); return add(this.pos, this.neg.times(k - 1)).modBy(k); }
|
|
262
|
+
divBy(k) { divisor(k, 'divBy'); return new Int(add(this.pos, this.neg.times(k - 1)).divBy(k), this.neg); }
|
|
247
263
|
// |value|, and the value where it is not negative (0 below).
|
|
248
264
|
get abs() { return this.pos.minus(this.neg).plus(this.neg.minus(this.pos)); }
|
|
249
265
|
get clamped() { return this.pos.minus(this.neg); }
|
|
@@ -808,6 +824,32 @@ export const someKey = (f, witness) => {
|
|
|
808
824
|
const none = everyOk(scope?.keys, k => f(keyTerm(k)).not().ok);
|
|
809
825
|
return new Cond({ op: 'not', x: { op: 'all', key, c: f(v(key)).not().c, ...(witness === undefined ? {} : { witness: termOf(witness) }) } }, none === undefined ? undefined : !none);
|
|
810
826
|
};
|
|
827
|
+
export function exists(a, b, c) {
|
|
828
|
+
const [kind, f, w] = (a instanceof Kind ? [a, b, c] : [nat, a, b]);
|
|
829
|
+
const name = `some$${++boundCounter}`, value = kind.value(name), body = all(...kind.assume(name), f(value));
|
|
830
|
+
const store = (x) => Object.fromEntries(Object.entries(kind.store(name, x)).map(([n, t]) => [n, termOf(t)]));
|
|
831
|
+
const ghost = typeof w === 'object' && w !== null && 'step' in w && 'start' in w && typeof w.step === 'function';
|
|
832
|
+
const given = w === undefined ? {} : ghost
|
|
833
|
+
? { start: store(w.start), step: (env) => store(w.step(value, env)) }
|
|
834
|
+
: { terms: store(w) };
|
|
835
|
+
return new Cond(body.c, undefined, [...(body.ex ?? []), { vars: kind.vars(name), body: body.c, pos: true, ...given }]);
|
|
836
|
+
}
|
|
837
|
+
// A condition's existentials pulled out before it (each reading as "there exists"), their naturals renamed `${prefix}0`,
|
|
838
|
+
// `${prefix}1`, ... (an integer's second part keeping $neg), so a machine built again names them alike.
|
|
839
|
+
function pulledOut(cs, what, prefix) {
|
|
840
|
+
const bs = cs.flatMap(c => c.ex ?? []);
|
|
841
|
+
if (bs.some(b => !b.pos))
|
|
842
|
+
throw new Error(`${what}: exists(...) under a not (or on the left of implies) says "for every value"; state it as a law about every value of a param instead`);
|
|
843
|
+
const ren = {};
|
|
844
|
+
if (prefix !== undefined)
|
|
845
|
+
bs.forEach((b, i) => b.vars.forEach(n => { ren[n] = { op: 'var', name: n.endsWith('$neg') ? `${prefix}${i}$neg` : `${prefix}${i}` }; }));
|
|
846
|
+
const r = (c) => (prefix === undefined ? c : substVars(c, ren));
|
|
847
|
+
const vars = bs.flatMap(b => b.vars.map(n => (prefix === undefined ? n : ren[n].name)));
|
|
848
|
+
const out = cs.map(c => r(c.c)), stray = out.flatMap(c => namesIn(c)).filter(n => n.startsWith('some$') && !vars.includes(n) && !/^some\$[ap]\d/.test(n));
|
|
849
|
+
if (stray.length)
|
|
850
|
+
throw new Error(`${what}: ${stray[0]} is bound by an exists(...) whose condition was taken apart; keep it whole (and, or, not, implies)`);
|
|
851
|
+
return { cs: out, vars, bodies: bs.map(b => ({ vars: b.vars.map(n => (prefix === undefined ? n : ren[n].name)), body: r(b.body) })) };
|
|
852
|
+
}
|
|
811
853
|
// A map from naturals to naturals, every cell starting at `init`. One write per map per step.
|
|
812
854
|
export class MapRef {
|
|
813
855
|
name;
|
|
@@ -1572,6 +1614,8 @@ const fnValueOf = (d) => {
|
|
|
1572
1614
|
};
|
|
1573
1615
|
let keyCounter = 0;
|
|
1574
1616
|
const sources = new WeakMap();
|
|
1617
|
+
// A rule's existentials in its precondition and assumption (core/exists.ts): what encodeParams and encodeInput find.
|
|
1618
|
+
const skolemsOf = new WeakMap();
|
|
1575
1619
|
const withSource = (x, src) => (sources.set(x, src), x);
|
|
1576
1620
|
// A k-step law's later requests are built over marked names (j:name, which no rule can declare: ':' is reserved); the
|
|
1577
1621
|
// kernel reads them as name@j.
|
|
@@ -1768,11 +1812,37 @@ export class Rule {
|
|
|
1768
1812
|
const key = `key$${++keyCounter}`;
|
|
1769
1813
|
if (out instanceof Cond) {
|
|
1770
1814
|
const p = prenex(out.c, `law ${name}`, key);
|
|
1771
|
-
return p ? { name, forall: key, holds: p } : { name, holds: out.c };
|
|
1815
|
+
return this.divides(this.someOf(name, out, p ? { name, forall: key, holds: p } : { name, holds: out.c }));
|
|
1772
1816
|
}
|
|
1773
|
-
const [k1, more] = keysFor(out),
|
|
1774
|
-
return more.length ? { name, forall: k1, keys: more, holds } : { name, forall: k1, holds: prenex(holds, `law ${name}`, k1) ?? holds };
|
|
1775
|
-
}
|
|
1817
|
+
const [k1, more] = keysFor(out), inner = out.forall(v(k1), ...more.map(v)), holds = inner.c;
|
|
1818
|
+
return this.divides(this.someOf(name, inner, more.length ? { name, forall: k1, keys: more, holds } : { name, forall: k1, holds: prenex(holds, `law ${name}`, k1) ?? holds }));
|
|
1819
|
+
}
|
|
1820
|
+
// A law's existentials (exists), pulled out before it: `some` its naturals, `witness` the ones given (a ghost's step
|
|
1821
|
+
// read with the step's own values).
|
|
1822
|
+
someOf(name, out, law) {
|
|
1823
|
+
if (!out.ex?.length)
|
|
1824
|
+
return law;
|
|
1825
|
+
const { vars } = pulledOut([out], `law ${name}`), bs = out.ex;
|
|
1826
|
+
const terms = Object.assign({}, ...bs.map(b => b.terms ?? {}));
|
|
1827
|
+
const ghost = {};
|
|
1828
|
+
const env = bs.some(b => b.step) ? this.env({}) : {};
|
|
1829
|
+
for (const b of bs)
|
|
1830
|
+
if (b.start && b.step) {
|
|
1831
|
+
const st = b.step(env);
|
|
1832
|
+
for (const n of b.vars)
|
|
1833
|
+
ghost[n] = { start: b.start[n] ?? { op: 'const', value: 0 }, step: st[n] ?? { op: 'var', name: n } };
|
|
1834
|
+
}
|
|
1835
|
+
const witness = { ...(Object.keys(terms).length ? { terms } : {}), ...(Object.keys(ghost).length ? { ghost } : {}) };
|
|
1836
|
+
return { ...law, some: vars, ...(Object.keys(witness).length ? { witness } : {}) };
|
|
1837
|
+
}
|
|
1838
|
+
// What a law, a step law or a decision divides by, noted so the machine declares it (Spec.arith).
|
|
1839
|
+
divides(x) { if (this.spec.arith)
|
|
1840
|
+
divisorsIn([x], this.spec.arith); return x; }
|
|
1841
|
+
// Divide by these too (inferLaws' candidates): the machine declares div$k and mod$k for each.
|
|
1842
|
+
dividesBy(...ks) { for (const k of ks)
|
|
1843
|
+
(this.spec.arith ??= new Set()).add(divisor(k, 'dividesBy')); return this; }
|
|
1844
|
+
// The same rule dividing by these too, its own divisors left as they are.
|
|
1845
|
+
withDivisors(...ks) { return this.with({ arith: new Set([...(this.spec.arith ?? []), ...ks.map(k => divisor(k, 'withDivisors'))]) }); }
|
|
1776
1846
|
// What the kinds keep true of the state and of the maps and lists of a kind (Kind.invariant: a flag 0 or 1, a choice or
|
|
1777
1847
|
// a case in range, a presence bit 0 or 1), as laws: proveRule, proveSteps and proveLeadsTo prove them beside the laws
|
|
1778
1848
|
// they are given, so a law may lean on "the hold is idle, holding or released" without stating it; give them to
|
|
@@ -1805,10 +1875,10 @@ export class Rule {
|
|
|
1805
1875
|
const key = `key$${++keyCounter}`, src = { step: { then: then, ...(when ? { when: when } : {}) } };
|
|
1806
1876
|
if (out instanceof Cond) {
|
|
1807
1877
|
const p = prenex(out.c, `step law ${name}`, key);
|
|
1808
|
-
return withSource(p ? { name, forall: key, then: p, ...w } : { name, then: out.c, ...w }, src);
|
|
1878
|
+
return withSource(this.divides(p ? { name, forall: key, then: p, ...w } : { name, then: out.c, ...w }), src);
|
|
1809
1879
|
}
|
|
1810
1880
|
const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
|
|
1811
|
-
return withSource(more.length ? { name, forall: k1, keys: more, then: c, ...w } : { name, forall: k1, then: prenex(c, `step law ${name}`, k1) ?? c, ...w }, src);
|
|
1881
|
+
return withSource(this.divides(more.length ? { name, forall: k1, keys: more, then: c, ...w } : { name, forall: k1, then: prenex(c, `step law ${name}`, k1) ?? c, ...w }), src);
|
|
1812
1882
|
}
|
|
1813
1883
|
// A law about k steps in a row (k from 2 to 64): `then(first, last, requests)` relates what the first step reads to the
|
|
1814
1884
|
// state, maps and sums after the last, with each step's request and queries (requests[0] is the first's); `when(before,
|
|
@@ -1832,10 +1902,10 @@ export class Rule {
|
|
|
1832
1902
|
const key = `key$${++keyCounter}`;
|
|
1833
1903
|
if (out instanceof Cond) {
|
|
1834
1904
|
const p = prenex(out.c, `step law ${name}`, key);
|
|
1835
|
-
return p ? { name, steps: k, forall: key, then: marked(p), ...w } : { name, steps: k, then: marked(out.c), ...w };
|
|
1905
|
+
return this.divides(p ? { name, steps: k, forall: key, then: marked(p), ...w } : { name, steps: k, then: marked(out.c), ...w });
|
|
1836
1906
|
}
|
|
1837
1907
|
const [k1, more] = keysFor(out), c = out.forall(v(k1), ...more.map(v)).c;
|
|
1838
|
-
return more.length ? { name, steps: k, forall: k1, keys: more, then: marked(c), ...w } : { name, steps: k, forall: k1, then: marked(prenex(c, `step law ${name}`, k1) ?? c), ...w };
|
|
1908
|
+
return this.divides(more.length ? { name, steps: k, forall: k1, keys: more, then: marked(c), ...w } : { name, steps: k, forall: k1, then: marked(prenex(c, `step law ${name}`, k1) ?? c), ...w });
|
|
1839
1909
|
}
|
|
1840
1910
|
// "Whenever `from` holds, `to` holds later", in every run that takes each helpful request infinitely often: proven by
|
|
1841
1911
|
// a rank (a natural over the state and params, or several compared in order) that no step raises while `from` holds
|
|
@@ -1852,7 +1922,7 @@ export class Rule {
|
|
|
1852
1922
|
const req = (f) => Object.fromEntries(Object.entries(f(env)).map(([n, t]) => [n, termOf(t)]));
|
|
1853
1923
|
const r = spec.rank ? spec.rank(env) : [], rank = Array.isArray(r) ? r.map(termOf) : termOf(r);
|
|
1854
1924
|
const base = { name, from: spec.from(env).c, to: spec.to(env).c, rank };
|
|
1855
|
-
return typeof spec.helpful === 'function' ? { ...base, request: req(spec.helpful) } : { ...base, helpful: spec.helpful.map(h => ({ ...(h.when ? { when: h.when(env).c } : {}), request: req(h.request) })) };
|
|
1925
|
+
return this.divides(typeof spec.helpful === 'function' ? { ...base, request: req(spec.helpful) } : { ...base, helpful: spec.helpful.map(h => ({ ...(h.when ? { when: h.when(env).c } : {}), request: req(h.request) })) });
|
|
1856
1926
|
}
|
|
1857
1927
|
// A decision: whenever the params and the request meet `given`, `then` holds. Decisions don't read the state; the
|
|
1858
1928
|
// functions' facts are assumed at the arguments the decision reads them at, and a carried list's equations (each
|
|
@@ -1865,8 +1935,8 @@ export class Rule {
|
|
|
1865
1935
|
const t = then(env).c;
|
|
1866
1936
|
const lists = (this.spec.lists2 ?? []).map(([n]) => env[n]);
|
|
1867
1937
|
const listed = lists.flatMap(l => l.equationsAt(uniqueTerms([l.len, l.len.minus(1), ...keysRead([...hyps, t], l.maps)])).map(c => c.c));
|
|
1868
|
-
const goal = { name, hyps: [...listed, ...hyps, { op: 'not', x: t }] };
|
|
1869
|
-
const facts = this.spec.fns?.length || this.spec.facts?.length ? factsFor(this.machine, goal) : [];
|
|
1938
|
+
const goal = this.divides({ name, hyps: [...listed, ...hyps, { op: 'not', x: t }] });
|
|
1939
|
+
const facts = this.spec.fns?.length || this.spec.facts?.length || this.spec.arith?.size ? factsFor(this.machine, goal) : [];
|
|
1870
1940
|
const d = { name, rule: this, given: hyps, then: t, goal: { name, hyps: [...facts, ...goal.hyps] } };
|
|
1871
1941
|
if ([...(Array.isArray(g) ? g : [g]).map(c => c.c), t].some(quantifiedIn)) {
|
|
1872
1942
|
const conds = (e) => { const own = given(e); return { given$: all(...(Array.isArray(own) ? own : [own])), then$: then(e) }; };
|
|
@@ -2061,8 +2131,20 @@ export class Rule {
|
|
|
2061
2131
|
else
|
|
2062
2132
|
state[n] = hoist.term(termOf(u));
|
|
2063
2133
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2134
|
+
// An existential in a precondition or the assumption: a value the params or the request bring (a param or an input
|
|
2135
|
+
// the runtimes find, encodeParams and encodeInput), fresh for each existential and, for a request, each step.
|
|
2136
|
+
const pre0 = pulledOut(s.pre.map(f => f(pEnv)), `${s.name}: a precondition`, 'some$p'), as0 = pulledOut(s.assume.map(f => f(env)), `${s.name}: the assumption`, 'some$a');
|
|
2137
|
+
const known = new Set([...params, ...inputs]);
|
|
2138
|
+
for (const b of as0.bodies) {
|
|
2139
|
+
const out = namesIn(b.body).filter(n => !known.has(n) && !as0.vars.includes(n));
|
|
2140
|
+
if (out.length)
|
|
2141
|
+
throw new Error(`${s.name}: an exists(...) in the assumption reads ${out[0]}: it reads only the params and the request (its value comes with the request)`);
|
|
2142
|
+
}
|
|
2143
|
+
skolemsOf.set(this, { params: pre0.bodies, inputs: as0.bodies });
|
|
2144
|
+
params.push(...pre0.vars);
|
|
2145
|
+
inputs.push(...as0.vars);
|
|
2146
|
+
const pre = [...this.kindAssumptions('params'), ...pre0.cs];
|
|
2147
|
+
const assume = [...this.kindAssumptions('inputs'), ...(s.lists2 ?? []).flatMap(([n]) => env[n].equations().map(c => hoist.cond(c.c))), ...as0.cs.map(c => hoist.cond(c))];
|
|
2066
2148
|
placed();
|
|
2067
2149
|
// Functions, and their facts: each function's own (named after it), then the rule's.
|
|
2068
2150
|
// The functions a function reads come first, each named <function>.<use>.
|
|
@@ -2100,7 +2182,7 @@ export class Rule {
|
|
|
2100
2182
|
...(s.prune ? { prune: { order: pruneOrder[0], after: termOf(s.prune.after(pEnv)) } } : {}),
|
|
2101
2183
|
};
|
|
2102
2184
|
}
|
|
2103
|
-
return m;
|
|
2185
|
+
return withArithmetic(m, [], s.arith ?? []);
|
|
2104
2186
|
}
|
|
2105
2187
|
// --- the builders checked ------------------------------------------------------------------------------------------
|
|
2106
2188
|
// The builders against what they built, on concrete runs: random params and requests by their kinds (`pre` and
|
|
@@ -2275,7 +2357,26 @@ export class Rule {
|
|
|
2275
2357
|
}
|
|
2276
2358
|
// --- running it -----------------------------------------------------------------------------------------------------
|
|
2277
2359
|
// Runtime values (numbers, booleans, choice names, null for a missing value) to the variables the machine reads.
|
|
2278
|
-
encodeParams(values) { return encode(this.spec.params, values); }
|
|
2360
|
+
encodeParams(values) { return this.withSkolems('params', encode(this.spec.params, values)); }
|
|
2361
|
+
// The values the params or a request bring for the existentials of a precondition or the assumption: each a witness
|
|
2362
|
+
// found (exact for one natural or integer read linearly), or 0 where none is (the precondition or the assumption then
|
|
2363
|
+
// fails, so the params or the request are refused).
|
|
2364
|
+
withSkolems(which, out, params) {
|
|
2365
|
+
if (!skolemsOf.has(this))
|
|
2366
|
+
void this.machine;
|
|
2367
|
+
const sk = skolemsOf.get(this)?.[which] ?? [];
|
|
2368
|
+
if (!sk.length)
|
|
2369
|
+
return out;
|
|
2370
|
+
const m = this.machine, env = { ...params, ...numbersOf(out) };
|
|
2371
|
+
for (const b of sk) {
|
|
2372
|
+
const w = someHolds(b.body, b.vars, env, { scalars: {}, maps: {} }, m)?.witness ?? {};
|
|
2373
|
+
for (const n of b.vars) {
|
|
2374
|
+
out[n] = w[n] ?? 0;
|
|
2375
|
+
env[n] = w[n] ?? 0;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
return out;
|
|
2379
|
+
}
|
|
2279
2380
|
// A carried map is a Set of keys (each cell 1), an array of them, or a Map of key to natural; a sequence an array of
|
|
2280
2381
|
// naturals, a table of them an array of such arrays.
|
|
2281
2382
|
// A carried list is an array of its items' runtime values (its length and folds computed from them); `params`, what
|
|
@@ -2311,7 +2412,7 @@ export class Rule {
|
|
|
2311
2412
|
const given = values[n];
|
|
2312
2413
|
out[n] = given instanceof Map ? new Map(given) : new Map([...given].map(key => [key, 1]));
|
|
2313
2414
|
}
|
|
2314
|
-
return out;
|
|
2415
|
+
return this.withSkolems('inputs', out, params && this.encodeParams(params));
|
|
2315
2416
|
}
|
|
2316
2417
|
// Each carried list's folds at its end (what `L.fold.<f>` reads), computed from its items as encodeInput computes
|
|
2317
2418
|
// them, without running the rule: `foldsOf({Lines: [...]})` → {Lines: {total: 30}}. Only the lists given.
|
|
@@ -2326,7 +2427,7 @@ export class Rule {
|
|
|
2326
2427
|
// The state's variables declared with a kind (a yes/no, a choice, an integer...), with their kinds.
|
|
2327
2428
|
kindsOfState() { return this.spec.kinded ?? []; }
|
|
2328
2429
|
// Random params and inputs that meet their kinds (not the user's `pre` or `assume`, which a caller checks).
|
|
2329
|
-
randomParams(rand, bound) { return Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound))); }
|
|
2430
|
+
randomParams(rand, bound) { return this.withSkolems('params', Object.assign({}, ...this.spec.params.map(([n, k]) => k.random(n, rand, bound)))); }
|
|
2330
2431
|
// The params' values by their kinds, as constants (what a fold that reads them is computed with).
|
|
2331
2432
|
paramValues(ps) {
|
|
2332
2433
|
return Object.fromEntries(this.spec.params.map(([n, k]) => [n, k.build(n, part => new Term({ op: 'const', value: ps[part] }, ps[part]))]));
|
|
@@ -2341,7 +2442,7 @@ export class Rule {
|
|
|
2341
2442
|
const l = new CarriedList(n, k, v(`${n}_len`), fs, part => new MapRef(part, 0), {}, ps && this.paramValues(ps)).encode(Array.from({ length: rand(bound + 1) }, () => k.sample(rand, bound)));
|
|
2342
2443
|
Object.assign(out, l.maps, { [`${n}_len`]: l.len });
|
|
2343
2444
|
}
|
|
2344
|
-
return out;
|
|
2445
|
+
return this.withSkolems('inputs', out, ps);
|
|
2345
2446
|
}
|
|
2346
2447
|
// The state's scalars as runtime values: each kinded variable as its kind decodes it (an integer as one signed
|
|
2347
2448
|
// number, a decimal as its number, a choice by name, a string as itself, none as null).
|
|
@@ -2384,7 +2485,7 @@ const expandParts = (updates) => {
|
|
|
2384
2485
|
return updates;
|
|
2385
2486
|
};
|
|
2386
2487
|
const encode = (kinds, values) => Object.assign({}, ...kinds.map(([n, k]) => k.encode(n, values[n])));
|
|
2387
|
-
export const rule = (name) => new Rule({ name, params: [], inputs: [], state: [], maps: [], flags: [], pre: [], assume: [], sums: [], queries: [] });
|
|
2488
|
+
export const rule = (name) => new Rule({ name, params: [], inputs: [], state: [], maps: [], flags: [], pre: [], assume: [], sums: [], queries: [], arith: new Set() });
|
|
2388
2489
|
const quantifiedE = (e) => e.op === 'if' ? quantifiedIn(e.c) || quantifiedE(e.then) || quantifiedE(e.else) : 'a' in e && typeof e.a === 'object' ? quantifiedE(e.a) || ('b' in e && typeof e.b === 'object' && quantifiedE(e.b)) : false;
|
|
2389
2490
|
const quantifiedIn = (c) => c.op === 'all' || (c.op === 'not' ? quantifiedIn(c.x) : c.op === 'and' || c.op === 'or' ? c.xs.some(quantifiedIn) : c.op === 'flag' ? false : quantifiedE(c.a) || quantifiedE(c.b));
|
|
2390
2491
|
// The kind a result field is kept in.
|
|
@@ -2545,7 +2646,8 @@ const refuteBudget = { trials: 60, depth: 6, bound: 3, ms: 150 };
|
|
|
2545
2646
|
const deeperBudget = { trials: 600, depth: 24, bound: 8, ms: 1500, seed: 29 };
|
|
2546
2647
|
function refuteByRuns(r, laws, stepLaws = [], budget = refuteBudget) {
|
|
2547
2648
|
const m = r.machine;
|
|
2548
|
-
|
|
2649
|
+
// Division by a constant is a function too, but its facts fix its value, so a run computing it is a run of the rule.
|
|
2650
|
+
if (Object.keys(m.fns ?? {}).some(f => !isArithmetic(f)) || m.facts?.some(f => !isArithmetic(f.name.slice(0, f.name.indexOf(':')))))
|
|
2549
2651
|
return undefined;
|
|
2550
2652
|
const run = interpret(m), rand = seeded(budget.seed ?? 13), until = performance.now() + budget.ms, bound = budget.bound;
|
|
2551
2653
|
const shown = (input) => Object.fromEntries(Object.entries(input).map(([n, x]) => [n, typeof x === 'number' ? x : Object.fromEntries(x)]));
|
|
@@ -2610,15 +2712,21 @@ function refuteByRuns(r, laws, stepLaws = [], budget = refuteBudget) {
|
|
|
2610
2712
|
return undefined;
|
|
2611
2713
|
}
|
|
2612
2714
|
const refutedVerdict = (out) => ({ proven: false, checked: false, goal: out.goal, stuck: 'sat', witness: out.witness, ...(out.run ? { run: out.run } : {}) });
|
|
2715
|
+
// The divisors some laws read (modBy, divBy), noted on the rule so its machine declares them: a law may come from
|
|
2716
|
+
// another instance of the same rule (a factory called twice).
|
|
2717
|
+
const noting = (r, ...xs) => { const ks = divisorsIn(xs); if (ks.size)
|
|
2718
|
+
r.dividesBy(...ks); return r; };
|
|
2613
2719
|
export function proveRule(r, laws, opts = {}) {
|
|
2720
|
+
noting(r, laws);
|
|
2614
2721
|
const refuted = refuteByRuns(r, laws);
|
|
2615
2722
|
if (refuted)
|
|
2616
2723
|
return refutedVerdict(refuted);
|
|
2617
2724
|
const out = proveLaws(r.machine, withKinds(r, laws));
|
|
2618
|
-
const v = { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}) };
|
|
2725
|
+
const v = { proven: out.proven, checked: out.checked, ...(out.failed ? { goal: out.failed.goal, stuck: out.failed.stuck, ...(out.failed.witness ? { witness: out.failed.witness } : {}), ...(out.failed.why ? { why: out.failed.why } : {}) } : {}), ...(out.witnesses && out.proven ? { witnesses: out.witnesses } : {}) };
|
|
2619
2726
|
return searched(r, laws, [], v, opts);
|
|
2620
2727
|
}
|
|
2621
2728
|
export function proveSteps(r, laws, stepLaws, opts = {}) {
|
|
2729
|
+
noting(r, laws, stepLaws);
|
|
2622
2730
|
const refuted = refuteByRuns(r, laws, stepLaws);
|
|
2623
2731
|
if (refuted)
|
|
2624
2732
|
return refutedVerdict(refuted);
|
|
@@ -2663,6 +2771,9 @@ function searched(r, laws, stepLaws, v, opts) {
|
|
|
2663
2771
|
return { ...verdict, found: { laws: found, ...(lemmas ? { lemmas } : {}) } };
|
|
2664
2772
|
}
|
|
2665
2773
|
export function proveLeadsTo(r, laws, l, opts = {}) {
|
|
2774
|
+
noting(r, laws, l);
|
|
2775
|
+
if (laws.some(law => law.some?.length))
|
|
2776
|
+
throw new Error(`${r.machine.name}: a law with "there exists" goes to proveRule or proveSteps; a liveness takes plain laws (the ones its proof printed, Verdict.witnesses)`);
|
|
2666
2777
|
if (Array.isArray(l.rank) && !l.rank.length) {
|
|
2667
2778
|
const f = findLeadsTo(r.machine, withKinds(r, laws), l, { params: (rand, bound) => r.randomParams(rand, bound), input: (rand, bound, ps) => r.randomInput(rand, bound, ps) }, opts);
|
|
2668
2779
|
return { proven: f.proven, checked: f.checked, ...(f.goal ? { goal: f.goal, stuck: f.stuck } : {}), ...(f.witness ? { witness: f.witness } : {}), ...(f.why ? { why: f.why } : {}),
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type C, type E, type Law, type Machine, type SomeWitness, type State } from './machine.js';
|
|
2
|
+
import { type Report } from '../kernel/prove.js';
|
|
3
|
+
type Env = Record<string, number>;
|
|
4
|
+
type Group = string[];
|
|
5
|
+
export declare const namesOf: (t: E | C) => string[];
|
|
6
|
+
export declare function groupsOf(vars: string[]): Group[];
|
|
7
|
+
export declare function substVarsE(e: E, s: Record<string, E>): E;
|
|
8
|
+
export declare function substVars(c: C, s: Record<string, E>): C;
|
|
9
|
+
export declare function someHolds(body: C, vars: string[], env: Env, st: State, m: Machine): {
|
|
10
|
+
holds: boolean;
|
|
11
|
+
witness?: Env;
|
|
12
|
+
} | undefined;
|
|
13
|
+
export type SomeReport = Report & {
|
|
14
|
+
laws?: Law[];
|
|
15
|
+
machine?: Machine;
|
|
16
|
+
ghosts?: string[];
|
|
17
|
+
witnesses?: Record<string, string>;
|
|
18
|
+
proof?: unknown;
|
|
19
|
+
};
|
|
20
|
+
export declare function proveSome(m: Machine, laws: Law[], opts?: {
|
|
21
|
+
rounds?: number;
|
|
22
|
+
}): SomeReport;
|
|
23
|
+
export declare function showE(e: E): string;
|
|
24
|
+
export declare function showC(c: C): string;
|
|
25
|
+
export declare const someLaws: (laws: Law[]) => boolean;
|
|
26
|
+
export type { SomeWitness };
|