gdp-wired 0.17.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.
@@ -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
@@ -22,6 +22,7 @@ import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws, withDeadline }
22
22
  import { crossCyclic, cyclicOn, foldNodes, proveCyclic, quietly, recurNodes } from "./cyclic.js";
23
23
  import { discover } from "./discover.js";
24
24
  import { inferLaws } from "./infer.js";
25
+ import { cycleLemmas, findRank } from "./cycles.js";
25
26
  // --- terms and conditions -------------------------------------------------------------------------------------------
26
27
  const naturalConst = (v) => {
27
28
  if (!Number.isSafeInteger(v) || v < 0)
@@ -2483,6 +2484,7 @@ export class LoopRule {
2483
2484
  ...(assume.length ? { assume: assume.length === 1 ? assume[0] : { op: 'and', xs: assume } } : {}),
2484
2485
  };
2485
2486
  }
2487
+ get hasRank() { return !!this.spec.rank; }
2486
2488
  get rankTerms() {
2487
2489
  if (!this.spec.rank)
2488
2490
  throw new Error(`${this.spec.name}: no rank`);
@@ -2624,6 +2626,13 @@ function searched(r, laws, stepLaws, v, opts) {
2624
2626
  if (Object.keys(d.found).length)
2625
2627
  (rule = d.rule, lemmas = d.found);
2626
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
+ }
2627
2636
  const ms = until - performance.now();
2628
2637
  if (ms < 500)
2629
2638
  return v;
@@ -2667,7 +2676,15 @@ export function crossCheckDecision(d, opts = {}) {
2667
2676
  }
2668
2677
  return { checked };
2669
2678
  }
2670
- 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
+ };
2671
2688
  export class FoldRule {
2672
2689
  spec;
2673
2690
  constructor(spec) {
@@ -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: {
@@ -41,9 +44,11 @@ export declare function grow(goal: Goal, path?: Path, budget?: {
41
44
  until?: number;
42
45
  units?: boolean;
43
46
  }): Outcome;
47
+ export declare function widen(part: Goal, whole: Goal, tree: Tree, opaque?: Record<string, E>, pp?: Path, wp?: Path): Tree | undefined;
44
48
  export declare const minimal: {
45
49
  on: boolean;
46
50
  };
51
+ export declare function usedHyps(goal: Goal, tree: Tree, path?: Path, out?: Set<number>): Set<number>;
47
52
  export type Report = {
48
53
  proven: boolean;
49
54
  nodes: number;
@@ -1,4 +1,5 @@
1
1
  import { add, k, refuteFast, scale, solve, sub, v } from "../core/solver.js";
2
+ import { checkRefutation } from "../core/check.js";
2
3
  import { crossCyclic, cyclicMachine, cyclicOn, machineNodes, proveCyclic } from "../core/cyclic.js";
3
4
  import { assumed, atomOf, boundOf, canon, canonFlip, columnTerm, decided, factorsOf, isCell, isMonomial, stepZone, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, leafConstraintsReusing, reusing, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, witnessesOf, withProducts } from "./kernel.js";
4
5
  // The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
@@ -77,7 +78,10 @@ function openH(c, path, out, positive = true) {
77
78
  if (out.length === before && !positive && c.op === '=' && truth(c, path) === undefined)
78
79
  openC(c, path, out);
79
80
  }
80
- // Pairs of reads of one map whose keys the path doesn't yet compare.
81
+ // Pairs of reads of one map whose keys the path doesn't yet compare. With `pairedKeys.skip` (core/data.ts), reads at
82
+ // keys a pairing builds ((a + b)² + b, keys whose difference has monomials) are left uncompared: that encoding states
83
+ // their congruence as facts, and comparing their keys only brings products into leaves its linear rows decide.
84
+ export const pairedKeys = { skip: false };
81
85
  function openCells(rows, path) {
82
86
  const names = [...new Set(rows.flatMap(r => Object.keys(r.lin)))].filter(isCell);
83
87
  const out = [];
@@ -87,7 +91,27 @@ function openCells(rows, path) {
87
91
  if (p.slice(0, p.indexOf('[')) !== q.slice(0, q.indexOf('[')))
88
92
  continue;
89
93
  const d = sub(parse(keyText(p)), parse(keyText(q)));
94
+ if (pairedKeys.skip && Object.keys(d).some(isMonomial))
95
+ continue;
90
96
  const dec = (lin) => path.get(canon(lin))?.holds ?? (path.has(canonFlip(lin)) ? !path.get(canonFlip(lin)).holds : undefined);
97
+ // Two cells of a run's map (core/cyclic.ts: time u and key k at u + k·Ω) at keys k·Ω and k'·Ω: k = k' first, a
98
+ // linear atom; the keys' own difference only where k = k' (its other side then refuted by (k − k')·Ω = 0, a product).
99
+ const q0 = cellProducts.on ? quotient(d) : undefined;
100
+ if (q0) {
101
+ const qa = dec(q0), qb = dec(scale(q0, -1));
102
+ if (qa === undefined) {
103
+ out.push(q0);
104
+ continue;
105
+ }
106
+ if (qa === true && qb === undefined) {
107
+ out.push(scale(q0, -1));
108
+ continue;
109
+ }
110
+ if (!(qa && qb))
111
+ continue;
112
+ cellDiffs.set(canon(d), q0);
113
+ cellDiffs.set(canon(scale(d, -1)), scale(q0, -1));
114
+ }
91
115
  const a = dec(d), b = dec(scale(d, -1));
92
116
  if (a === undefined)
93
117
  out.push(d);
@@ -96,6 +120,24 @@ function openCells(rows, path) {
96
120
  }
97
121
  return out;
98
122
  }
123
+ // A key difference q·F over monomials that share one factor F (Ω), with no other part: q, else undefined.
124
+ const quotient = (d) => {
125
+ const keys = Object.keys(d);
126
+ if (!keys.length || keys.some(key => key === '1' || !isMonomial(key)))
127
+ return undefined;
128
+ const shared = factorsOf(keys[0]).find(f => keys.every(key => factorsOf(key).includes(f)));
129
+ if (!shared)
130
+ return undefined;
131
+ const out = {};
132
+ for (const key of keys) {
133
+ const fs = [...factorsOf(key)];
134
+ fs.splice(fs.indexOf(shared), 1);
135
+ out[fs.join('*')] = (out[fs.join('*')] ?? 0) + d[key];
136
+ }
137
+ return out;
138
+ };
139
+ // The cells' key differences split at their quotient's equality, by canonical text: their quotient.
140
+ const cellDiffs = new Map();
99
141
  function parse(text) {
100
142
  const out = {};
101
143
  let depth = 0, start = 0;
@@ -225,7 +267,7 @@ function growPlain(goal, path, budget) {
225
267
  return { stuck: 'unknown', path, why: outOfBudget(goal, budget.nodes > 200_000 ? 'nodes' : 'time', path.size) };
226
268
  // Rows that read a term column only once a cut has put one on the path; before, the rows a choice-free search uses
227
269
  // (what it saw before term columns), renumbered to the whole leaf's when a leaf is found.
228
- const full = timed(() => leafConstraintsReusing(goal, path), ms => { profile.rowsMs += ms; });
270
+ const full = timed(() => rowsAt(goal, path), ms => { profile.rowsMs += ms; });
229
271
  const columns = [...path.values()].some(a => Object.keys(a.lin).some(key => key.startsWith('⟦')));
230
272
  const idx = full.flatMap((r, i) => (columns || !Object.keys(r.lin).some(key => key.startsWith('⟦')) ? [i] : []));
231
273
  const rows = idx.map(i => full[i]), at = (i) => (i < rows.length ? idx[i] : full.length + i - rows.length);
@@ -278,6 +320,21 @@ function growPlain(goal, path, budget) {
278
320
  return { tree: { leaf: fast.steps }, nodes: budget.nodes };
279
321
  }
280
322
  let split = next ?? (verdict.kind === 'unknown' ? bisect(rows, path) ?? fractional(verdict.point, path) : undefined);
323
+ // A cell difference's failing side (k = k' on the path, the keys apart): (k − k')·Ω = 0 refutes it, a product of each
324
+ // of the path's two atoms k − k' <= 0 and k' − k <= 0 with Ω >= 0.
325
+ if (cellProducts.on && path.size) {
326
+ const [id, last] = [...path].at(-1), q0 = !last.holds ? cellDiffs.get(id) : undefined;
327
+ if (q0) {
328
+ const at = (lin) => rows.findIndex(r => canon(r.lin) === canon(lin));
329
+ const d = last.lin, f = factorsOf(Object.keys(d)[0]).find(x => !Object.keys(q0).includes(x) && Object.keys(d).every(key => factorsOf(key).includes(x)));
330
+ const e1 = at(q0), e2 = at(scale(q0, -1)), sign = f === undefined ? -1 : at({ [f]: -1 });
331
+ if (e1 >= 0 && e2 >= 0 && sign >= 0) {
332
+ const pairs = [[e1, sign], [e2, sign]], out = solve(withProducts(rows, pairs));
333
+ if (out.kind === 'refuted')
334
+ return { tree: leafOf(out.steps, pairs), nodes: budget.nodes };
335
+ }
336
+ }
337
+ }
281
338
  // Products: with monomials about, try every pair of rows multiplied; failing that, split a factor at zero.
282
339
  const monomials = rows.some(r => Object.keys(r.lin).some(isMonomial));
283
340
  // A witness that multiplies out (each monomial's value is its factors' product) is a real model: nothing to refute.
@@ -500,6 +557,24 @@ function squareForms(rows) {
500
557
  // the decision's own row, and any row only it makes, are not there), every split kept. The derivations are the same
501
558
  // arithmetic over the same rows, and the kernel checks the whole tree in the end. Undefined at the first leaf that
502
559
  // needs the decision.
560
+ // A leaf's rows, by its path's decisions in order (what they are a function of): a lift reads every leaf of a subtree
561
+ // at its own path, found when it was grown, and at the path without the split, which the next lift up reads again.
562
+ const rowsSeen = new WeakMap();
563
+ function rowsAt(goal, path) {
564
+ let byPath = rowsSeen.get(goal);
565
+ if (!byPath)
566
+ rowsSeen.set(goal, (byPath = new Map()));
567
+ let id = '';
568
+ for (const [key, a] of path)
569
+ id += `${a.holds ? '+' : '-'}${key}|`;
570
+ let rows = byPath.get(id);
571
+ if (!rows) {
572
+ if (byPath.size >= 4096)
573
+ byPath.clear();
574
+ byPath.set(id, (rows = leafConstraintsReusing(goal, path)));
575
+ }
576
+ return rows;
577
+ }
503
578
  function lift(goal, tree, from, to) {
504
579
  const own = to.size; // the decision's row: the last of `from`'s path rows, below every later split's
505
580
  const reads = (t) => ('leaf' in t ? t.leaf.some(st => st.kind === 'input' && st.index === own) || (t.products ?? []).some(([i, j]) => i === own || j === own) : reads(t.yes) || reads(t.no));
@@ -514,7 +589,7 @@ function lift(goal, tree, from, to) {
514
589
  const no = yes && go(t.no, new Map(a).set(sid, { lin: t.split, holds: false }), new Map(b).set(sid, { lin: t.split, holds: false }));
515
590
  return yes && no ? { split: t.split, yes, no } : undefined;
516
591
  }
517
- const ra = leafConstraintsReusing(goal, a), rb = leafConstraintsReusing(goal, b), at = new Map();
592
+ const ra = rowsAt(goal, a), rb = rowsAt(goal, b), at = new Map();
518
593
  rb.forEach((r, i) => { const key = canon(r.lin); if (!at.has(key))
519
594
  at.set(key, i); });
520
595
  const map = (i) => (i < ra.length ? at.get(canon(ra[i].lin)) : undefined);
@@ -540,6 +615,86 @@ function lift(goal, tree, from, to) {
540
615
  };
541
616
  return go(tree, from, to);
542
617
  }
618
+ // A tree for a weaker goal (some hypotheses left out, some conjuncts of one left out) made a tree for the whole: at any
619
+ // path a leaf's rows are among the whole goal's (each hypothesis' facts are a hypothesis' own, the cells, terms and
620
+ // naturals they read a subset), so each input is renumbered to the same row there, a square or product past the rows
621
+ // moved by the difference. Where a dropped conjunct makes the whole hypothesis false under the path, its row 1 <= 0
622
+ // refutes the leaf alone; a leaf neither maps is grown again on the whole goal.
623
+ // `opaque`: variables the weaker goal has for terms of the whole (a step's value, read the same in two hypotheses), each
624
+ // read as the term's linear form at the whole goal's path; a split the whole's path already decides is followed, not
625
+ // made again.
626
+ export function widen(part, whole, tree, opaque = {}, pp = new Map(), wp = pp) {
627
+ const names = Object.keys(opaque);
628
+ const rename = (lin) => {
629
+ if (!names.length)
630
+ return lin;
631
+ let out = {};
632
+ for (const [key, c] of Object.entries(lin)) {
633
+ const e = opaque[key];
634
+ if (e) {
635
+ const l = linear(e, wp);
636
+ if (!l)
637
+ return undefined;
638
+ out = add(out, scale(l, c));
639
+ }
640
+ else if (names.some(n => key.includes(n)))
641
+ return undefined;
642
+ else
643
+ out = add(out, { [key]: c });
644
+ }
645
+ return out;
646
+ };
647
+ if (!('leaf' in tree)) {
648
+ const id = canon(tree.split), wl = rename(tree.split);
649
+ if (!wl) {
650
+ const o = grow(whole, wp);
651
+ return 'tree' in o ? o.tree : undefined;
652
+ }
653
+ const wid = canon(wl), known = Object.keys(wl).every(key => key === '1') ? (wl['1'] ?? 0) <= 0 : wp.has(wid) ? wp.get(wid).holds : wp.has(canonFlip(wl)) ? !wp.get(canonFlip(wl)).holds : undefined;
654
+ if (known !== undefined)
655
+ return widen(part, whole, known ? tree.yes : tree.no, opaque, new Map(pp).set(id, { lin: tree.split, holds: known }), wp);
656
+ const yes = widen(part, whole, tree.yes, opaque, new Map(pp).set(id, { lin: tree.split, holds: true }), new Map(wp).set(wid, { lin: wl, holds: true }));
657
+ const no = yes && widen(part, whole, tree.no, opaque, new Map(pp).set(id, { lin: tree.split, holds: false }), new Map(wp).set(wid, { lin: wl, holds: false }));
658
+ return yes && no ? { split: wl, yes, no } : undefined;
659
+ }
660
+ const rp = rowsAt(part, pp), rw = rowsAt(whole, wp), at = new Map();
661
+ rw.forEach((r, i) => { const key = canon(r.lin); if (!at.has(key))
662
+ at.set(key, i); });
663
+ const map = (i) => { if (i >= rp.length)
664
+ return i - rp.length + rw.length; const l = rename(rp[i].lin); return l && at.get(canon(l)); };
665
+ const leaf = [], products = [], squares = (tree.squares ?? []).map(rename);
666
+ let ok = squares.every(Boolean);
667
+ for (const st of ok ? tree.leaf : []) {
668
+ if (st.kind !== 'input') {
669
+ leaf.push(st);
670
+ continue;
671
+ }
672
+ const j = map(st.index);
673
+ if (j === undefined) {
674
+ ok = false;
675
+ break;
676
+ }
677
+ leaf.push({ ...st, index: j });
678
+ }
679
+ for (const [i, j] of ok ? tree.products ?? [] : []) {
680
+ const pi = map(i), pj = map(j);
681
+ if (pi === undefined || pj === undefined) {
682
+ ok = false;
683
+ break;
684
+ }
685
+ products.push([pi, pj]);
686
+ }
687
+ // (renamed, a derivation through products or cuts is replayed: a cut's divisor may not divide the terms' coefficients)
688
+ if (ok && names.length && !checkRefutation(withProducts(rw, products, squares), leaf))
689
+ ok = false;
690
+ if (ok)
691
+ return { leaf, ...(tree.products ? { products } : {}), ...(tree.squares ? { squares: squares } : {}) };
692
+ const absurd = rw.findIndex(r => Object.keys(r.lin).every(key => key === '1') && (r.lin['1'] ?? 0) > 0);
693
+ if (absurd >= 0)
694
+ return { leaf: [{ kind: 'input', index: absurd }] };
695
+ const out = grow(whole, wp);
696
+ return 'tree' in out ? out.tree : undefined;
697
+ }
543
698
  // Whether each hypothesis reads only names the goal's conclusion (its last hypothesis) reads.
544
699
  const nearSeen = new WeakMap();
545
700
  const namesOf = (t) => (t === undefined ? [] : [...JSON.stringify(t).matchAll(/"(?:name|map)":"([^"]+)"/g)].map(m => m[1]));
@@ -1051,9 +1206,9 @@ function triggered(m, laws, inst, build, focus = []) {
1051
1206
  // fewer hypotheses (a law about pairs of items with 900 instance hypotheses and 30 leaves using a few of them, say).
1052
1207
  export const minimal = { on: false };
1053
1208
  // The hypotheses a tree's leaves read: each leaf's input rows, and the rows its products multiply.
1054
- function usedHyps(goal, tree, path = new Map(), out = new Set()) {
1209
+ export function usedHyps(goal, tree, path = new Map(), out = new Set()) {
1055
1210
  if ('leaf' in tree) {
1056
- const rows = leafConstraints(goal, path), n = rows.length, sq = tree.squares?.length ?? 0;
1211
+ const rows = rowsAt(goal, path), n = rows.length, sq = tree.squares?.length ?? 0;
1057
1212
  const mark = (i) => {
1058
1213
  if (i < n) {
1059
1214
  const s = rows[i].src;
@@ -37,6 +37,7 @@ export declare function readable(id: string, laws: Law[], vars: string[], maps:
37
37
  binders: string;
38
38
  hyp: string;
39
39
  call: string;
40
+ have?: (j: number) => string;
40
41
  }): string;
41
42
  export type ProgressCase = {
42
43
  id: string;
@@ -61,6 +62,10 @@ export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts
61
62
  stepLaws?: StepLaw[];
62
63
  }): string;
63
64
  export declare const hasSets: (m: Machine) => boolean;
65
+ export declare function machineLit(mm: Machine, t: {
66
+ e: (x: E) => string;
67
+ c: (x: C) => string;
68
+ }, ix: (name: string) => number, mx: (name: string) => number): string;
64
69
  export declare function smachineToLean(id: string, given: Machine, laws: Law[], opts?: {
65
70
  stepLaws?: StepLaw[];
66
71
  liveness?: Liveness[];
@@ -152,6 +157,13 @@ export type LeanItem = {
152
157
  proof: Cyclic;
153
158
  }[];
154
159
  after?: (ix: (name: string) => number, mx: (map: string) => number) => string;
160
+ bridge?: CycBridge;
161
+ recur?: CycRecur;
162
+ fold?: {
163
+ fold: Fold;
164
+ laws: Law[];
165
+ };
166
+ data?: true;
155
167
  };
156
168
  export declare function toLean(items: LeanItem[], opts?: {
157
169
  title?: string;
@@ -160,7 +172,55 @@ export declare function toLean(items: LeanItem[], opts?: {
160
172
  export declare function leanProject(file: string, opts?: {
161
173
  name?: string;
162
174
  }): Record<string, string>;
175
+ export type CycBridge = {
176
+ machine: Machine;
177
+ laws: Law[];
178
+ tampered?: {
179
+ what: string;
180
+ machine: Machine;
181
+ }[];
182
+ };
183
+ export type CycRecur = {
184
+ name: string;
185
+ args: string[];
186
+ zero: E;
187
+ next: E;
188
+ lemmas: {
189
+ name: string;
190
+ holds: C;
191
+ }[];
192
+ between: {
193
+ name: string;
194
+ holds: C;
195
+ }[];
196
+ };
163
197
  export declare function cyclicToLean(id: string, p: Cyclic, tampered?: {
164
198
  what: string;
165
199
  proof: Cyclic;
166
- }[], after?: (ix: (name: string) => number, mx: (map: string) => number) => string): string;
200
+ }[], after?: (ix: (name: string) => number, mx: (map: string) => number) => string, bridge?: CycBridge, recur?: CycRecur, fold?: {
201
+ fold: Fold;
202
+ laws: Law[];
203
+ }): string;
204
+ export declare function cycEncodingLean(id: string, facts: {
205
+ name: string;
206
+ keys: string[];
207
+ holds: C;
208
+ }[], nodes: {
209
+ name: string;
210
+ hyps: C[];
211
+ concl: C;
212
+ }[], b: CycBridge): string;
213
+ export declare function cycRecurCheckLean(id: string, facts: {
214
+ name: string;
215
+ keys: string[];
216
+ holds: C;
217
+ }[], r: CycRecur): string;
218
+ export declare function cycFoldCheckLean(id: string, facts: {
219
+ name: string;
220
+ keys: string[];
221
+ holds: C;
222
+ }[], nodes: {
223
+ name: string;
224
+ hyps: C[];
225
+ concl: C;
226
+ }[], f: Fold, laws: Law[]): string;