gdp-wired 0.19.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.
@@ -83,8 +83,8 @@ function linearCands(target, forms, samples, show) {
83
83
  return dedupe(out.filter(c => samples.every(s => holds(c, s))));
84
84
  }
85
85
  const dedupe = (cs) => [...new Map(cs.map(c => [c.name, c])).values()];
86
- // A polynomial in x of degree 2 at most, exact over the rationals, as d·f = a·x·x + b·x + c with whole numbers: fitted to
87
- // three points, kept when every sample meets it.
86
+ // A polynomial in x of degree 3 at most, exact over the rationals, as d·f = a·x·x·x + b·x·x + c·x + e with whole
87
+ // numbers: fitted to four points (Newton's differences), kept when every sample meets it.
88
88
  function closedForm(samples, show) {
89
89
  const at = new Map();
90
90
  for (const s of samples) {
@@ -95,22 +95,30 @@ function closedForm(samples, show) {
95
95
  if (![0, 1, 2].every(x => at.has(x)))
96
96
  return [];
97
97
  const [f0, f1, f2] = [at.get(0), at.get(1), at.get(2)];
98
+ const keep = (lin) => {
99
+ const g = Object.values(lin).reduce((p, q) => gcd(p, Math.abs(q)), 0);
100
+ const c = { name: '', rel: '=', lin: Object.fromEntries(Object.entries(lin).map(([a, k]) => [a, k / g]).filter(([, k]) => k !== 0)) };
101
+ c.name = nameOf(c, show, 'f');
102
+ return samples.every(s => holds(c, s)) ? [c] : [];
103
+ };
98
104
  // f = a x² + b x + c: c = f0, a = (f2 - 2 f1 + f0) / 2, b = f1 - f0 - a; times 2 to keep whole numbers.
99
105
  const A = f2 - 2 * f1 + f0, B = 2 * (f1 - f0) - A, C = 2 * f0;
100
- if (A === 0)
101
- return []; // linear: the bounds find it
102
- const g = [A, B, C, 2].reduce((p, q) => gcd(p, Math.abs(q)), 0);
103
- const c = { name: '', rel: '=', lin: Object.fromEntries(Object.entries({ f: 2 / g, xx: -A / g, x: -B / g, 1: -C / g }).filter(([, k]) => k !== 0)) };
104
- c.name = nameOf(c, show, 'f');
105
- return samples.every(s => holds(c, s)) ? [c] : [];
106
+ const square = A === 0 ? [] : keep({ f: 2, xx: -A, x: -B, 1: -C });
107
+ if (square.length || !at.has(3))
108
+ return square;
109
+ // f = Δ0 + Δ1·x + Δ2·x(x − 1)/2 + Δ3·x(x − 1)(x − 2)/6, times 6: x³ by Δ3, x² by 3Δ2 − 3Δ3, x by 6Δ1 − 3Δ2 + 2Δ3.
110
+ const d1 = f1 - f0, d2 = f2 - 2 * f1 + f0, d3 = at.get(3) - 3 * f2 + 3 * f1 - f0;
111
+ if (d3 === 0)
112
+ return []; // degree 2 at most: above, or linear (the bounds find it)
113
+ return keep({ f: 6, xxx: -d3, xx: -(3 * d2 - 3 * d3), x: -(6 * d1 - 3 * d2 + 2 * d3), 1: -6 * f0 });
106
114
  }
107
115
  // The law a goal is of (the kernel names each goal after its law: '<law> @step').
108
116
  const lawOfGoal = (goal) => goal.replace(/ @(start|step|step law|\d+ steps|leaf|node)$/, '');
109
117
  const isGoalOf = (goal) => (c) => { const law = lawOfGoal(goal); return law === c.name || law === `${c.name} at y`; };
110
118
  const gcd = (a, b) => (b === 0 ? Math.abs(a) : gcd(b, a % b));
111
119
  // --- recurrences ---------------------------------------------------------------------------------------------------
112
- const showRecur = (a) => (a === 'f' ? 'f(x)' : a === 'g' ? 'f(y)' : a === 'xx' ? 'x·x' : a);
113
- const srcRecur = (a) => (a === 'xx' ? 'x.mul(x)' : a);
120
+ const showRecur = (a) => (a === 'f' ? 'f(x)' : a === 'g' ? 'f(y)' : a === 'xx' ? 'x·x' : a === 'xxx' ? 'x·x·x' : a);
121
+ const srcRecur = (a) => (a === 'xx' ? 'x.mul(x)' : a === 'xxx' ? 'x.mul(x).mul(x)' : a);
114
122
  // The function's values: x from 0 to 40 at a few values of each further argument (all of them for two at most).
115
123
  function recurSamples(d, args) {
116
124
  const f = (typeof d.js === 'function' ? d.js : compileFunction([], `return (${d.js})`)());
@@ -128,7 +136,7 @@ function recurSamples(d, args) {
128
136
  break;
129
137
  throw e;
130
138
  }
131
- out.push({ x, f: y, xx: x * x, ...Object.fromEntries(args.map((a, i) => [a, t[i]])) });
139
+ out.push({ x, f: y, xx: x * x, xxx: x * x * x, ...Object.fromEntries(args.map((a, i) => [a, t[i]])) });
132
140
  }
133
141
  return out;
134
142
  }
@@ -167,9 +175,9 @@ function recurCands(samples, args) {
167
175
  c.name = nameOf(c, showRecur, 'g');
168
176
  return { lemmas, between };
169
177
  }
170
- const recurAtoms = (args, x, f, env, y, g) => ({ x, f, xx: x.mul(x), ...(y && g ? { y, g } : {}), ...Object.fromEntries(args.map(a => [a, env[a]])) });
178
+ const recurAtoms = (args, x, f, env, y, g) => ({ x, f, xx: x.mul(x), xxx: x.mul(x).mul(x), ...(y && g ? { y, g } : {}), ...Object.fromEntries(args.map(a => [a, env[a]])) });
171
179
  const recurSrc = (c, args, between) => {
172
- const reads = (a) => Object.keys(c.lin).includes(a) || (a === 'x' && 'xx' in c.lin);
180
+ const reads = (a) => Object.keys(c.lin).includes(a) || (a === 'x' && ('xx' in c.lin || 'xxx' in c.lin));
173
181
  const names = between ? ['x', 'f', 'y', 'g'] : ['x', 'f'];
174
182
  let unused = 0;
175
183
  const params = [...names.map(n => (reads(n) ? n : '_'.repeat(++unused))), ...(args.some(reads) ? [`{${args.filter(reads).join(', ')}}`] : [])];
@@ -268,27 +276,40 @@ function discoverSearch(r, laws, stepLaws, first) {
268
276
  if (first.proven && first.checked)
269
277
  return { verdict: first, rule: r, found: {} };
270
278
  const { params, fns, lists } = r.parts, ps = natParams(params);
271
- // Per function and per list: what is proven, made into the rule.
272
- const fnMade = {};
273
- for (const [n, d] of fns) {
274
- const src = recurOf(d);
275
- if (!src)
276
- continue;
277
- const args = [...(src.def.args ?? [])], { lemmas, between } = recurCands(recurSamples(d, args), args);
278
- const made = houdiniRecur(d, lemmas, between);
279
- if (made && (made.lemmas.length || made.between.length))
280
- fnMade[n] = made;
281
- }
282
- let rule = r.withParts(Object.fromEntries(Object.entries(fnMade).map(([n, m]) => [n, m.d])));
283
- const listKept = {};
284
- for (const part of lists) {
285
- const out = houdiniList(rule, part, listCands(part, params, listSamples(part, params)), ps);
286
- if (out && out.kept.length) {
287
- rule = out.rule;
288
- listKept[part[0]] = out.kept;
279
+ // Per function and per list: what is proven, made into the rule. A function with a closed form (of degree 2 or 3,
280
+ // fitted exactly) first with it alone: it decides the function, and the bounds beside it, which the closed form
281
+ // implies only through products, are slow to prove by induction; the rest where the laws aren't proven with it.
282
+ const cands = new Map(fns.flatMap(([n, d]) => { const src = recurOf(d); if (!src)
283
+ return []; const args = [...(src.def.args ?? [])]; return [[n, recurCands(recurSamples(d, args), args)]]; }));
284
+ const exact = (c) => c.rel === '=' && ('xx' in c.lin || 'xxx' in c.lin);
285
+ const makeFns = (closedOnly) => {
286
+ const out = {};
287
+ for (const [n, d] of fns) {
288
+ const cs = cands.get(n);
289
+ if (!cs)
290
+ continue;
291
+ const closed = cs.lemmas.filter(exact), made = closedOnly && closed.length ? houdiniRecur(d, closed, []) : houdiniRecur(d, cs.lemmas, cs.between);
292
+ if (made && (made.lemmas.length || made.between.length))
293
+ out[n] = made;
289
294
  }
290
- }
291
- const verdict = proveSteps(rule, laws, stepLaws, { search: false });
295
+ return out;
296
+ };
297
+ const withLists = (fnMade) => {
298
+ let rule = r.withParts(Object.fromEntries(Object.entries(fnMade).map(([n, m]) => [n, m.d])));
299
+ const listKept = {};
300
+ for (const part of lists) {
301
+ const out = houdiniList(rule, part, listCands(part, params, listSamples(part, params)), ps);
302
+ if (out && out.kept.length) {
303
+ rule = out.rule;
304
+ listKept[part[0]] = out.kept;
305
+ }
306
+ }
307
+ return { fnMade, rule, listKept, verdict: proveSteps(rule, laws, stepLaws, { search: false }) };
308
+ };
309
+ let made = [...cands.values()].some(cs => cs.lemmas.some(exact)) ? withLists(makeFns(true)) : undefined;
310
+ if (!made || !(made.verdict.proven && made.verdict.checked))
311
+ made = withLists(makeFns(false));
312
+ const { fnMade, rule, listKept, verdict } = made;
292
313
  const found = () => ({
293
314
  ...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)) } : {}) }]; })),
294
315
  ...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)) }])),
@@ -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,13 +608,17 @@ 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;
594
618
  leadsTo(name: string, spec: {
595
619
  from: (env: P & S & M & Su) => Cond;
596
620
  to: (env: P & S & M & Su) => Cond;
597
- rank: (env: P & S & M & Su) => TermLike | TermLike[];
621
+ rank?: (env: P & S & M & Su) => TermLike | TermLike[];
598
622
  helpful: ((env: P & S & M & Su) => Record<string, TermLike>) | {
599
623
  when?: (env: P & S & M & Su) => Cond;
600
624
  request: (env: P & S & M & Su) => Record<string, TermLike>;
@@ -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?: {
@@ -828,7 +854,9 @@ export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj
828
854
  export declare function proveSteps(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], stepLaws: StepLaw[], opts?: ProveOptions): Verdict;
829
855
  export type { StepLaw };
830
856
  export type { Liveness };
831
- export declare function proveLeadsTo(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], l: Liveness): Verdict;
857
+ export declare function proveLeadsTo(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], l: Liveness, opts?: {
858
+ ms?: number;
859
+ }): Verdict;
832
860
  export declare const proveDecision: (d: Decision) => Verdict;
833
861
  export declare function crossCheckDecision(d: Decision, opts?: {
834
862
  trials?: number;