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.
@@ -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
- constructor(c: C, ok?: boolean | undefined);
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
- constructor(c, ok) {
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
- 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)));
84
- 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)));
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), holds = out.forall(v(k1), ...more.map(v)).c;
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
- const pre = [...this.kindAssumptions('params'), ...s.pre.map(f => f(pEnv).c)];
2065
- const assume = [...this.kindAssumptions('inputs'), ...(s.lists2 ?? []).flatMap(([n]) => env[n].equations().map(c => hoist.cond(c.c))), ...s.assume.map(f => hoist.cond(f(env).c))];
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
- if (Object.keys(m.fns ?? {}).length || m.facts?.length)
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 };