gdp-wired 0.16.0 → 0.18.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,7 @@
10
10
  // const out = discover(r, laws, stepLaws)
11
11
  // out.verdict // the laws, proven of out.rule: r with its functions and lists made again with the lemmas
12
12
  // out.found // per function or list, the lemmas found, as source to paste into recur(...) or carriesList(...)
13
+ import { quietly } from "./cyclic.js";
13
14
  import { any, atMost, compileFunction, evalC, evalE, n, plus, prod, same, times, x } from "./machine.js";
14
15
  import { Unproven, lit, proveFold, proveSteps, recur, recurOf } from "./dsl.js";
15
16
  const seeded = (seed) => { let s = seed; return (k) => { s = (s * 1103515245 + 12345) % 2 ** 31; return Math.floor(s / 2 ** 31 * k); }; };
@@ -260,7 +261,10 @@ function houdiniList(r, part, cands, params) {
260
261
  // 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
262
  // is proven first, then each dropped in turn while the laws stay proven without it.
262
263
  // `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
+ export function discover(r, laws, stepLaws = [], first) {
265
+ return quietly(() => discoverSearch(r, laws, stepLaws, first ?? proveSteps(r, laws, stepLaws, { search: false })));
266
+ }
267
+ function discoverSearch(r, laws, stepLaws, first) {
264
268
  if (first.proven && first.checked)
265
269
  return { verdict: first, rule: r, found: {} };
266
270
  const { params, fns, lists } = r.parts, ps = natParams(params);
@@ -405,7 +409,8 @@ const eOfSide = (side) => {
405
409
  return es.length ? es.slice(1).reduce((acc, t) => plus(acc, t), es[0]) : n(0);
406
410
  };
407
411
  const cOfCand = (c) => { const { pos, neg } = sides(c.lin), a = eOfSide(pos), b = eOfSide(neg); return c.rel === '=' ? same(a, b) : atMost(a, b); };
408
- export function discoverFold(f, laws) {
412
+ export function discoverFold(f, laws) { return quietly(() => discoverFoldSearch(f, laws)); }
413
+ function discoverFoldSearch(f, laws) {
409
414
  const stated = f.lemmas ?? [], first = proveFold({ fold: f.fold, lemmas: stated }, laws);
410
415
  if (first.proven && first.checked)
411
416
  return { verdict: first, lemmas: [], found: [] };
@@ -781,6 +781,7 @@ export declare class LoopRule<P extends Obj = {}, I extends Obj = {}, S extends
781
781
  law(name: string, f: (env: P & S) => Cond): Law;
782
782
  private env;
783
783
  get loop(): Loop;
784
+ get hasRank(): boolean;
784
785
  get rankTerms(): E[];
785
786
  randomParams(rand: Rand, bound: number): Record<string, number>;
786
787
  randomInput(rand: Rand, bound: number): Record<string, number>;
@@ -810,10 +811,13 @@ export type Found = {
810
811
  lemmas?: string[];
811
812
  between?: string[];
812
813
  }>;
814
+ facts?: string[];
815
+ rank?: string;
813
816
  };
814
817
  export type ProveOptions = {
815
818
  search?: boolean | {
816
819
  ms?: number;
820
+ cycles?: boolean;
817
821
  };
818
822
  };
819
823
  export declare class Unproven extends Error {
@@ -834,7 +838,9 @@ export declare function crossCheckDecision(d: Decision, opts?: {
834
838
  checked: number;
835
839
  failure?: string;
836
840
  };
837
- export declare const proveLoop: (l: LoopRule<Obj, Obj, Obj>, laws: Law[]) => Verdict;
841
+ export declare const proveLoop: (l: LoopRule<Obj, Obj, Obj>, laws: Law[], opts?: {
842
+ ms?: number;
843
+ }) => Verdict;
838
844
  export type TreeValue<I> = {
839
845
  leaf: I;
840
846
  } | {
package/dist/core/dsl.js CHANGED
@@ -18,9 +18,11 @@
18
18
  import { carriedView, compileFunction, evalC, evalE, interpret, loadJs, numbersOf, pairedArgs } from "./machine.js";
19
19
  import { allowed, keysOf, requestEnv, watch } from "./watch.js";
20
20
  import { check, foldObligations, loopObligations } from "../kernel/kernel.js";
21
- import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws } from "../kernel/prove.js";
21
+ import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws, withDeadline } from "../kernel/prove.js";
22
+ import { crossCyclic, cyclicOn, foldNodes, proveCyclic, quietly, recurNodes } from "./cyclic.js";
22
23
  import { discover } from "./discover.js";
23
24
  import { inferLaws } from "./infer.js";
25
+ import { cycleLemmas, findRank } from "./cycles.js";
24
26
  // --- terms and conditions -------------------------------------------------------------------------------------------
25
27
  const naturalConst = (v) => {
26
28
  if (!Number.isSafeInteger(v) || v < 0)
@@ -1399,6 +1401,10 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1399
1401
  const stated = Object.entries(lemmas).map(([n, f]) => { const holds = f(named('x'), named('fx'), envAt(own)).c; only(`lemma "${n}"`, holds, ['x', 'fx', ...args], uses.map(([u]) => u)); return { name: n, holds }; });
1400
1402
  const pairs = Object.entries(between).map(([n, f]) => { const holds = f(named('x'), named('fx'), named('y'), named('fy'), envAt(own)).c; only(`law between "${n}"`, holds, ['x', 'fx', 'y', 'fy', ...args], uses.map(([u]) => u)); return { name: n, holds }; });
1401
1403
  const argKinds = Object.fromEntries(args.map(a => [a, nat]));
1404
+ // The recurrence as a cyclic proof's instance (core/cyclic.ts, GDP_CYCLIC=1): the facts of the functions it reads, from
1405
+ // a rule that reads them.
1406
+ const recurCase = (between) => ({ name, args, zero, next, lemmas: stated, between,
1407
+ facts: uses.length ? rule(`${name}'s functions`).fns(def.fns ?? {}).inputs({}).state({ z: 0 }).step((env) => ({ z: env.z })).machine.facts ?? [] : [] });
1402
1408
  const failed = (what, v) => { if (!(v.proven && v.checked))
1403
1409
  throw new Unproven(`${name}'s ${what}`, v); };
1404
1410
  // The lemmas: x counting up from 0 and fx its value, each lemma a law of that machine.
@@ -1413,7 +1419,11 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1413
1419
  const r = r0.state({ x: 0, fx: ((p) => (zeroReads ? p.fx0 : zeroAt(args.map(a => p[a])))) })
1414
1420
  .step((env) => ({ x: env.x.plus(1), fx: def.next(env.x, env.fx, envAt(args.map(a => env[a]))) }));
1415
1421
  const laws = Object.entries(lemmas).map(([n, f]) => r.law(n, (env) => f(env.x, env.fx, envAt(args.map(a => env[a])))));
1416
- verdict = proveRule(r, laws, { search: false });
1422
+ // (Its machine's proof timed alone: the recurrence is compared below as its own instance.)
1423
+ const t0 = performance.now();
1424
+ verdict = quietly(() => proveRule(r, laws, { search: false }));
1425
+ if (cyclicOn())
1426
+ crossCyclic('recurrence', name, verdict.proven && verdict.checked, () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0);
1417
1427
  failed('lemmas', verdict);
1418
1428
  proofs.lemmas = { machine: r.machine, laws };
1419
1429
  }
@@ -1450,7 +1460,9 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1450
1460
  .step((env) => ({ y: env.y.plus(1), fy: def.next(env.y, env.fy, envAt(at(env))) }));
1451
1461
  const laws = [...Object.entries(lemmas).map(([n, l]) => r.law(`${n} at y`, (env) => l(env.y, env.fy, envAt(at(env))))),
1452
1462
  ...Object.entries(between).map(([n, l]) => r.law(n, (env) => l(env.x, env.fx, env.y, env.fy, envAt(at(env)))))];
1453
- const v = proveRule(r, laws, { search: false });
1463
+ const t0 = performance.now(), v = quietly(() => proveRule(r, laws, { search: false })), usualMs = performance.now() - t0;
1464
+ if (cyclicOn())
1465
+ crossCyclic('recurrence, between', name, v.proven && v.checked, () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs);
1454
1466
  failed('laws between two points', v);
1455
1467
  proofs.between = { machine: r.machine, laws };
1456
1468
  verdict = { proven: true, checked: verdict.checked && v.checked };
@@ -2472,6 +2484,7 @@ export class LoopRule {
2472
2484
  ...(assume.length ? { assume: assume.length === 1 ? assume[0] : { op: 'and', xs: assume } } : {}),
2473
2485
  };
2474
2486
  }
2487
+ get hasRank() { return !!this.spec.rank; }
2475
2488
  get rankTerms() {
2476
2489
  if (!this.spec.rank)
2477
2490
  throw new Error(`${this.spec.name}: no rank`);
@@ -2607,12 +2620,19 @@ function searched(r, laws, stepLaws, v, opts) {
2607
2620
  let rule = r, lemmas;
2608
2621
  const { fns, lists } = r.parts;
2609
2622
  if (lists.length || fns.some(([, d]) => recurOf(d))) {
2610
- const d = discover(r, laws, stepLaws, v);
2623
+ const d = withDeadline(until, () => discover(r, laws, stepLaws, v));
2611
2624
  if (d.verdict.proven && d.verdict.checked)
2612
2625
  return { ...d.verdict, found: { lemmas: d.found } };
2613
2626
  if (Object.keys(d.found).length)
2614
2627
  (rule = d.rule, lemmas = d.found);
2615
2628
  }
2629
+ // Functions known by their facts alone (not a recurrence): lemmas about them, fitted to their values and proven from
2630
+ // the facts as cyclic claims, the laws proven with them as facts.
2631
+ if ((typeof opts.search !== 'object' || opts.search.cycles !== false) && fns.some(([, d]) => !recurOf(d)) && rule.machine.facts?.length && until - performance.now() > 500) {
2632
+ const c = withDeadline(until, () => cycleLemmas(rule.machine, withKinds(rule, laws), stepLaws, { ms: (until - performance.now()) / 2 }));
2633
+ if (c.proven && c.checked)
2634
+ return { proven: true, checked: true, found: { ...(lemmas ? { lemmas } : {}), facts: c.found.map(f => f.text) } };
2635
+ }
2616
2636
  const ms = until - performance.now();
2617
2637
  if (ms < 500)
2618
2638
  return v;
@@ -2656,7 +2676,15 @@ export function crossCheckDecision(d, opts = {}) {
2656
2676
  }
2657
2677
  return { checked };
2658
2678
  }
2659
- export const proveLoop = (l, laws) => goalsVerdict(loopObligations(l.loop, laws, l.rankTerms));
2679
+ // With no rank stated, the rank is searched for from the loop's runs (core/cycles.ts's findRank), with the invariants it
2680
+ // needs (inferLaws), within `ms` (10 s); the verdict is the kernel's on the rank and the laws found, in `found`.
2681
+ export const proveLoop = (l, laws, opts = {}) => {
2682
+ if (l.hasRank)
2683
+ return goalsVerdict(loopObligations(l.loop, laws, l.rankTerms));
2684
+ const out = findRank(l.loop, laws, opts);
2685
+ return { proven: out.proven, checked: out.checked, ...(out.trees ? { trees: out.trees } : {}), ...(out.goal ? { goal: out.goal, stuck: out.stuck } : {}), ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}),
2686
+ ...(out.found ? { found: { rank: out.found.rank, ...(out.laws?.length ? { laws: out.laws.map((law, i) => ({ law, text: out.found.laws[i] ?? law.name })) } : {}) } } : {}) };
2687
+ };
2660
2688
  export class FoldRule {
2661
2689
  spec;
2662
2690
  constructor(spec) {
@@ -2771,7 +2799,12 @@ export class FoldRule {
2771
2799
  }
2772
2800
  export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
2773
2801
  // A fold's laws proven over every tree (a rose fold's own laws, its children's lemmas, with them).
2774
- export const proveFold = (f, laws) => goalsVerdict(foldObligations(f.fold, [...(f.lemmas ?? []), ...laws]));
2802
+ export const proveFold = (f, laws) => {
2803
+ const all = [...(f.lemmas ?? []), ...laws], t0 = performance.now(), v = goalsVerdict(foldObligations(f.fold, all)), usualMs = performance.now() - t0;
2804
+ if (cyclicOn())
2805
+ crossCyclic('fold', f.fold.name, v.proven && v.checked, () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs);
2806
+ return v;
2807
+ };
2775
2808
  const asTerm = (t) => (t instanceof Term ? t : lit(t));
2776
2809
  // A fold over trees whose nodes have any number of children, as the kernel's binary fold (so the kernel and Lean's
2777
2810
  // fold_sound check it as they check any fold): a node's children become a chain of glue nodes, each folding one child
@@ -13,10 +13,11 @@
13
13
  // its witness is a counterexample to induction, so the candidate that rules it out (and the most of those seen) is
14
14
  // added; a chosen candidate that nothing supports, or that makes a proof slow, is dropped; the set proven as a whole
15
15
  // is the answer, after each chosen candidate the proof doesn't need is taken out again
16
+ import { quietly } from "./cyclic.js";
16
17
  import { any, atMost, evalC, get, interpret, n, not, plus, same, times, x } from "./machine.js";
17
18
  import { proveRule, proveSteps } from "./dsl.js";
18
19
  import { allowed, watch } from "./watch.js";
19
- import { proveLaw, proveLaws, proveStepLaws } from "../kernel/prove.js";
20
+ import { proveLaw, proveLaws, proveStepLaws, withDeadline } from "../kernel/prove.js";
20
21
  const KEY = 'key$found', AT = '@', K = '#';
21
22
  const seeded = (seed) => { let s = seed; return k => { s = (s * 1103515245 + 12345) % 2 ** 31; return Math.floor(s / 2 ** 31 * k); }; };
22
23
  const isRule = (t) => typeof t.kindsOfState === 'function';
@@ -106,6 +107,9 @@ const keyValue = (cell, w) => {
106
107
  return value;
107
108
  };
108
109
  export function inferLaws(target, laws, stepLaws = [], opts = {}) {
110
+ return quietly(() => withDeadline(performance.now() + (opts.ms ?? 20000), () => inferLawsSearch(target, laws, stepLaws, opts)));
111
+ }
112
+ function inferLawsSearch(target, laws, stepLaws = [], opts = {}) {
109
113
  const t0 = performance.now(), until = t0 + (opts.ms ?? 20000), rule = isRule(target) ? target : undefined;
110
114
  const m = rule ? rule.machine : target;
111
115
  let proofs = 0;
@@ -0,0 +1,45 @@
1
+ import type { C, E } from '../core/machine.js';
2
+ import { type Goal, type Tree } from './kernel.js';
3
+ export type Fact = {
4
+ name: string;
5
+ keys: string[];
6
+ holds: C;
7
+ };
8
+ export type Use = {
9
+ node: number;
10
+ subst: Record<string, E>;
11
+ guard: C;
12
+ premises: Tree[];
13
+ descent: Tree;
14
+ };
15
+ export type CNode = {
16
+ name: string;
17
+ hyps: C[];
18
+ concl: C;
19
+ rank: E[];
20
+ insts: {
21
+ fact: number;
22
+ at: E[];
23
+ }[];
24
+ uses: Use[];
25
+ main: Tree;
26
+ };
27
+ export type Cyclic = {
28
+ facts: Fact[];
29
+ nodes: CNode[];
30
+ };
31
+ export declare const falseC: C;
32
+ export declare const lexLt: (as: E[], bs: E[]) => C;
33
+ export declare const instHyps: (facts: Fact[], insts: CNode["insts"]) => C[];
34
+ export declare const baseHyps: (facts: Fact[], n: Pick<CNode, "hyps" | "insts">) => C[];
35
+ export declare const useHyps: (nodes: Pick<CNode, "concl">[], uses: Pick<Use, "node" | "subst" | "guard">[]) => C[];
36
+ type Shape = Pick<CNode, 'name' | 'hyps' | 'concl' | 'rank' | 'insts'> & {
37
+ uses: Pick<Use, 'node' | 'subst' | 'guard'>[];
38
+ };
39
+ export declare const mainGoal: (facts: Fact[], nodes: Shape[], n: Shape) => Goal;
40
+ export declare const premiseGoal: (facts: Fact[], n: Shape, u: Shape["uses"][number], c: C, i: number) => Goal;
41
+ export declare const descentGoal: (facts: Fact[], n: Shape, u: Shape["uses"][number], m: Shape) => Goal;
42
+ export declare const factsAvoid: (facts: Fact[], subst: Record<string, E>) => boolean;
43
+ export declare function refuseCyclic(p: Cyclic): string | undefined;
44
+ export declare const checkCyclic: (p: Cyclic) => boolean;
45
+ export {};
@@ -0,0 +1,54 @@
1
+ import { check, substC, substE } from "./kernel.js";
2
+ const zero = { op: 'const', value: 0 };
3
+ export const falseC = { op: '<', a: zero, b: zero };
4
+ // as below bs in order: the first place they differ, as's is smaller (Lean's lexLtC, built the same way).
5
+ export const lexLt = (as, bs) => (as.length && bs.length
6
+ ? { op: 'or', xs: [{ op: '<', a: as[0], b: bs[0] }, { op: 'and', xs: [{ op: '=', a: as[0], b: bs[0] }, lexLt(as.slice(1), bs.slice(1))] }] }
7
+ : falseC);
8
+ const sub = (s) => ({ vars: s, maps: {}, flags: {} });
9
+ // A fact at the terms a node names: its keys paired with the terms (the shorter list decides, as Lean's zip does).
10
+ const keyed = (keys, at) => Object.fromEntries(keys.slice(0, at.length).map((key, i) => [key, at[i]]));
11
+ export const instHyps = (facts, insts) => insts.flatMap(p => { const f = facts[p.fact]; return f ? [substC(f.holds, sub(keyed(f.keys, p.at)))] : []; });
12
+ export const baseHyps = (facts, n) => [...n.hyps, ...instHyps(facts, n.insts)];
13
+ export const useHyps = (nodes, uses) => uses.flatMap(u => {
14
+ const m = nodes[u.node];
15
+ return m ? [{ op: 'or', xs: [{ op: 'not', x: u.guard }, substC(m.concl, sub(u.subst))] }] : [];
16
+ });
17
+ export const mainGoal = (facts, nodes, n) => ({ name: `${n.name}`, hyps: [...baseHyps(facts, n), ...useHyps(nodes, n.uses), { op: 'not', x: n.concl }] });
18
+ export const premiseGoal = (facts, n, u, c, i) => ({ name: `${n.name}: use of node ${u.node}, its hypothesis ${i}`, hyps: [...baseHyps(facts, n), u.guard, { op: 'not', x: substC(c, sub(u.subst)) }] });
19
+ export const descentGoal = (facts, n, u, m) => ({ name: `${n.name}: use of node ${u.node}, its rank below`, hyps: [...baseHyps(facts, n), u.guard, { op: 'not', x: lexLt(m.rank.map(e => substE(e, sub(u.subst))), n.rank) }] });
20
+ const occurs = (name, t) => JSON.stringify(t).includes(`"op":"var","name":${JSON.stringify(name)}`);
21
+ // The language Lean's twin has: no flag, no quantifier.
22
+ const plain = (t) => !/"op":"(?:flag|all)"/.test(JSON.stringify(t));
23
+ export const factsAvoid = (facts, subst) => Object.keys(subst).every(v => facts.every(f => f.keys.includes(v) || !occurs(v, f.holds)));
24
+ // Why a cyclic proof is refused, or undefined when it checks.
25
+ export function refuseCyclic(p) {
26
+ const { facts, nodes } = p;
27
+ if (!plain(facts) || !plain(nodes.map(n => [n.hyps, n.concl, n.rank, n.insts, n.uses.map(u => [u.subst, u.guard])])))
28
+ return 'a flag or a quantifier (no cyclic proof reads one)';
29
+ for (const f of facts)
30
+ if (new Set(f.keys).size !== f.keys.length)
31
+ return `fact ${f.name}: a key twice`;
32
+ for (const n of nodes) {
33
+ if (!check(mainGoal(facts, nodes, n), n.main))
34
+ return `${n.name}: its goal`;
35
+ for (const [j, u] of n.uses.entries()) {
36
+ const m = nodes[u.node];
37
+ if (!m)
38
+ return `${n.name}: use ${j} names no node`;
39
+ if (m.rank.length !== n.rank.length)
40
+ return `${n.name}: use ${j} compares ranks of different lengths`;
41
+ if (!factsAvoid(facts, u.subst))
42
+ return `${n.name}: use ${j} assigns a variable a fact reads`;
43
+ if (m.hyps.length !== u.premises.length)
44
+ return `${n.name}: use ${j} has ${u.premises.length} premise trees for ${m.hyps.length} hypotheses`;
45
+ for (const [i, c] of m.hyps.entries())
46
+ if (!check(premiseGoal(facts, n, u, c, i), u.premises[i]))
47
+ return `${n.name}: use ${j}, hypothesis ${i} of ${m.name}`;
48
+ if (!check(descentGoal(facts, n, u, m), u.descent))
49
+ return `${n.name}: use ${j} doesn't lower the rank`;
50
+ }
51
+ }
52
+ return undefined;
53
+ }
54
+ export const checkCyclic = (p) => refuseCyclic(p) === undefined;
@@ -1,5 +1,8 @@
1
1
  import type { C, E, Law, Machine } from '../core/machine.js';
2
2
  import { type Goal, type Instances, type Liveness, type Path, type StepLaw, type Tree } from './kernel.js';
3
+ export declare const pairedKeys: {
4
+ skip: boolean;
5
+ };
3
6
  export declare const profile: {
4
7
  on: boolean;
5
8
  goals: {
@@ -21,6 +24,11 @@ export declare const solveKinds: Record<string, {
21
24
  ms: number;
22
25
  rows: number;
23
26
  }>;
27
+ export declare let proveDump: ((goal: Goal) => void) | undefined;
28
+ export declare const setProveDump: (f: ((goal: Goal) => void) | undefined) => void;
29
+ export declare const cellProducts: {
30
+ on: boolean;
31
+ };
24
32
  export type Outcome = {
25
33
  tree: Tree;
26
34
  nodes: number;
@@ -30,10 +38,17 @@ export type Outcome = {
30
38
  path: Path;
31
39
  why?: string;
32
40
  };
41
+ export declare function withDeadline<T>(until: number, f: () => T): T;
33
42
  export declare function grow(goal: Goal, path?: Path, budget?: {
34
43
  nodes: number;
35
44
  until?: number;
45
+ units?: boolean;
36
46
  }): Outcome;
47
+ export declare function widen(part: Goal, whole: Goal, tree: Tree, opaque?: Record<string, E>, pp?: Path, wp?: Path): Tree | undefined;
48
+ export declare const minimal: {
49
+ on: boolean;
50
+ };
51
+ export declare function usedHyps(goal: Goal, tree: Tree, path?: Path, out?: Set<number>): Set<number>;
37
52
  export type Report = {
38
53
  proven: boolean;
39
54
  nodes: number;