gdp-wired 0.18.0 → 0.20.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)) }])),
@@ -594,7 +594,7 @@ export declare class Rule<P extends Obj = {}, I extends Obj = {}, S extends Obj
594
594
  leadsTo(name: string, spec: {
595
595
  from: (env: P & S & M & Su) => Cond;
596
596
  to: (env: P & S & M & Su) => Cond;
597
- rank: (env: P & S & M & Su) => TermLike | TermLike[];
597
+ rank?: (env: P & S & M & Su) => TermLike | TermLike[];
598
598
  helpful: ((env: P & S & M & Su) => Record<string, TermLike>) | {
599
599
  when?: (env: P & S & M & Su) => Cond;
600
600
  request: (env: P & S & M & Su) => Record<string, TermLike>;
@@ -828,7 +828,9 @@ export declare function proveRule(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj
828
828
  export declare function proveSteps(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], stepLaws: StepLaw[], opts?: ProveOptions): Verdict;
829
829
  export type { StepLaw };
830
830
  export type { Liveness };
831
- export declare function proveLeadsTo(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], l: Liveness): Verdict;
831
+ export declare function proveLeadsTo(r: Rule<Obj, Obj, Obj, Obj, Obj, Obj, Obj, Obj>, laws: Law[], l: Liveness, opts?: {
832
+ ms?: number;
833
+ }): Verdict;
832
834
  export declare const proveDecision: (d: Decision) => Verdict;
833
835
  export declare function crossCheckDecision(d: Decision, opts?: {
834
836
  trials?: number;
package/dist/core/dsl.js CHANGED
@@ -18,11 +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, withDeadline } from "../kernel/prove.js";
22
- import { crossCyclic, cyclicOn, foldNodes, proveCyclic, quietly, recurNodes } from "./cyclic.js";
21
+ import { factsFor, grow, proveLaws, proveLiveness, proveStepLaws, usualProof, withDeadline } from "../kernel/prove.js";
22
+ import { certCyclic, certOn, crossCyclic, cyclicOn, foldNodes, proveCyclic, quietly, recurNodes, translatedProof, uncovered } 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
+ import { cycleLemmas, findLeadsTo, findRank } from "./cycles.js";
26
26
  // --- terms and conditions -------------------------------------------------------------------------------------------
27
27
  const naturalConst = (v) => {
28
28
  if (!Number.isSafeInteger(v) || v < 0)
@@ -1410,6 +1410,10 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1410
1410
  // The lemmas: x counting up from 0 and fx its value, each lemma a law of that machine.
1411
1411
  let verdict = { proven: true, checked: true };
1412
1412
  const proofs = {};
1413
+ // The usual proofs, to translate (core/cyclic.ts's recurNodes): the lemmas' machine's, the laws between's.
1414
+ let lemmaProof;
1415
+ const translatedRecur = (u) => (u.lemmas || !stated.length)
1416
+ ? st => translatedProof(() => JSON.stringify(['recurrence', recurCase(u.between ? pairs : [])]), () => recurNodes(recurCase(u.between ? pairs : []), u, st), st) : undefined;
1413
1417
  if (stated.length) {
1414
1418
  // A start that reads a function is a param given as that start by a fact (a start reads the params alone).
1415
1419
  const zeroReads = JSON.stringify(zero).includes('"op":"get"');
@@ -1421,9 +1425,17 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1421
1425
  const laws = Object.entries(lemmas).map(([n, f]) => r.law(n, (env) => f(env.x, env.fx, envAt(args.map(a => env[a])))));
1422
1426
  // (Its machine's proof timed alone: the recurrence is compared below as its own instance.)
1423
1427
  const t0 = performance.now();
1428
+ usualProof.last = undefined;
1424
1429
  verdict = quietly(() => proveRule(r, laws, { search: false }));
1430
+ lemmaProof = usualProof.last;
1425
1431
  if (cyclicOn())
1426
- crossCyclic('recurrence', name, verdict.proven && verdict.checked, () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0);
1432
+ crossCyclic('recurrence', name, verdict.proven && verdict.checked, () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0, translatedRecur({ lemmas: lemmaProof }));
1433
+ // (the usual trees checked already, inside quietly: where no cyclic proof is made they are the certificate)
1434
+ if (certOn() && verdict.proven && verdict.checked) {
1435
+ const c = certCyclic('recurrence', name, translatedRecur({ lemmas: lemmaProof }), () => { const { facts, specs } = recurNodes(recurCase([])); return proveCyclic(facts, specs); }, performance.now() - t0);
1436
+ if (!c.checked)
1437
+ uncovered('recurrence', name, c.why);
1438
+ }
1427
1439
  failed('lemmas', verdict);
1428
1440
  proofs.lemmas = { machine: r.machine, laws };
1429
1441
  }
@@ -1460,9 +1472,15 @@ export const recur = (name, def, lemmas = {}, between = {}) => {
1460
1472
  .step((env) => ({ y: env.y.plus(1), fy: def.next(env.y, env.fy, envAt(at(env))) }));
1461
1473
  const laws = [...Object.entries(lemmas).map(([n, l]) => r.law(`${n} at y`, (env) => l(env.y, env.fy, envAt(at(env))))),
1462
1474
  ...Object.entries(between).map(([n, l]) => r.law(n, (env) => l(env.x, env.fx, env.y, env.fy, envAt(at(env)))))];
1463
- const t0 = performance.now(), v = quietly(() => proveRule(r, laws, { search: false })), usualMs = performance.now() - t0;
1475
+ usualProof.last = undefined;
1476
+ const t0 = performance.now(), v = quietly(() => proveRule(r, laws, { search: false })), usualMs = performance.now() - t0, betweenProof = usualProof.last;
1464
1477
  if (cyclicOn())
1465
- crossCyclic('recurrence, between', name, v.proven && v.checked, () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs);
1478
+ crossCyclic('recurrence, between', name, v.proven && v.checked, () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs, betweenProof && translatedRecur({ lemmas: lemmaProof, between: betweenProof }));
1479
+ if (certOn() && v.proven && v.checked) {
1480
+ const c = certCyclic('recurrence, between', name, betweenProof && translatedRecur({ lemmas: lemmaProof, between: betweenProof }), () => { const { facts, specs } = recurNodes(recurCase(pairs)); return proveCyclic(facts, specs); }, usualMs);
1481
+ if (!c.checked)
1482
+ uncovered('recurrence, between', name, c.why);
1483
+ }
1466
1484
  failed('laws between two points', v);
1467
1485
  proofs.between = { machine: r.machine, laws };
1468
1486
  verdict = { proven: true, checked: verdict.checked && v.checked };
@@ -1828,10 +1846,11 @@ export class Rule {
1828
1846
  // rank: ({t, served}) => t.plus(1).minus(served), helpful: () => ({op: 1})})
1829
1847
  // batches.leadsTo('every batch is done', {from: () => yes, to: ({waiting, items}) => waiting.eq(0).and(items.eq(0)),
1830
1848
  // rank: ({waiting, items}) => [waiting, items], helpful: [{when: ({items}) => items.gt(0), request: () => ({op: 1})}, {when: ({items}) => items.eq(0), request: () => ({op: 0})}]})
1849
+ // With no rank, proveLeadsTo finds one from the rule's runs (core/cycles.ts's findLeadsTo).
1831
1850
  leadsTo(name, spec) {
1832
1851
  const env = this.env({ flags: false, inputs: false, queries: false });
1833
1852
  const req = (f) => Object.fromEntries(Object.entries(f(env)).map(([n, t]) => [n, termOf(t)]));
1834
- const r = spec.rank(env), rank = Array.isArray(r) ? r.map(termOf) : termOf(r);
1853
+ const r = spec.rank ? spec.rank(env) : [], rank = Array.isArray(r) ? r.map(termOf) : termOf(r);
1835
1854
  const base = { name, from: spec.from(env).c, to: spec.to(env).c, rank };
1836
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) })) };
1837
1856
  }
@@ -2504,7 +2523,7 @@ export class Unproven extends Error {
2504
2523
  this.verdict = verdict;
2505
2524
  }
2506
2525
  }
2507
- const goalsVerdict = (goals) => {
2526
+ const goalsVerdict = (goals, checkIt = true) => {
2508
2527
  const trees = {};
2509
2528
  for (const g of goals) {
2510
2529
  const out = grow(g);
@@ -2512,7 +2531,7 @@ const goalsVerdict = (goals) => {
2512
2531
  return { proven: false, checked: false, goal: g.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) };
2513
2532
  trees[g.name] = out.tree;
2514
2533
  }
2515
- return { proven: true, checked: goals.every(g => check(g, trees[g.name])), trees };
2534
+ return { proven: true, checked: !checkIt || goals.every(g => check(g, trees[g.name])), trees };
2516
2535
  };
2517
2536
  // The kinds' laws first, those the caller didn't pass already (by name).
2518
2537
  const withKinds = (r, laws) => [...r.kindLaws().filter(k => !laws.some(l => l.name === k.name)), ...laws];
@@ -2643,7 +2662,12 @@ function searched(r, laws, stepLaws, v, opts) {
2643
2662
  void _stats;
2644
2663
  return { ...verdict, found: { laws: found, ...(lemmas ? { lemmas } : {}) } };
2645
2664
  }
2646
- export function proveLeadsTo(r, laws, l) {
2665
+ export function proveLeadsTo(r, laws, l, opts = {}) {
2666
+ if (Array.isArray(l.rank) && !l.rank.length) {
2667
+ 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
+ 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 } : {}),
2669
+ ...(f.found ? { found: { rank: f.found.rank, ...(f.laws?.length ? { laws: f.laws.map((law, i) => ({ law, text: f.found.laws[i] ?? law.name })) } : {}) } } : {}) };
2670
+ }
2647
2671
  const out = proveLiveness(r.machine, withKinds(r, laws), l);
2648
2672
  return { 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 } : {}) } : {}) };
2649
2673
  }
@@ -2679,6 +2703,8 @@ export function crossCheckDecision(d, opts = {}) {
2679
2703
  // With no rank stated, the rank is searched for from the loop's runs (core/cycles.ts's findRank), with the invariants it
2680
2704
  // needs (inferLaws), within `ms` (10 s); the verdict is the kernel's on the rank and the laws found, in `found`.
2681
2705
  export const proveLoop = (l, laws, opts = {}) => {
2706
+ if (cyclicOn() || certOn())
2707
+ uncovered('loop', l.loop.name, 'a claim about runs that never end');
2682
2708
  if (l.hasRank)
2683
2709
  return goalsVerdict(loopObligations(l.loop, laws, l.rankTerms));
2684
2710
  const out = findRank(l.loop, laws, opts);
@@ -2800,9 +2826,18 @@ export class FoldRule {
2800
2826
  export const fold = (name) => new FoldRule({ name, params: [], inputs: [], pre: [], assume: [] });
2801
2827
  // A fold's laws proven over every tree (a rose fold's own laws, its children's lemmas, with them).
2802
2828
  export const proveFold = (f, laws) => {
2803
- const all = [...(f.lemmas ?? []), ...laws], t0 = performance.now(), v = goalsVerdict(foldObligations(f.fold, all)), usualMs = performance.now() - t0;
2829
+ // (by default the certificate a cyclic proof, core/cyclic.ts's certCyclic, the usual trees its input; where none is
2830
+ // made, the usual trees checked)
2831
+ const all = [...(f.lemmas ?? []), ...laws], t0 = performance.now(), v = goalsVerdict(foldObligations(f.fold, all), !certOn()), usualMs = performance.now() - t0;
2804
2832
  if (cyclicOn())
2805
- crossCyclic('fold', f.fold.name, v.proven && v.checked, () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs);
2833
+ crossCyclic('fold', f.fold.name, v.proven && v.checked, () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs, v.trees && (st => translatedProof(() => JSON.stringify(['fold', f.fold, all]), () => foldNodes(f.fold, all, v.trees, st), st)));
2834
+ if (certOn() && v.proven) {
2835
+ const c = certCyclic('fold', f.fold.name, v.trees && (st => translatedProof(() => JSON.stringify(['fold', f.fold, all]), () => foldNodes(f.fold, all, v.trees, st), st)), () => { const { facts, specs } = foldNodes(f.fold, all); return proveCyclic(facts, specs); }, usualMs);
2836
+ if (!c.checked) {
2837
+ uncovered('fold', f.fold.name, c.why);
2838
+ v.checked = foldObligations(f.fold, all).every(g => v.trees?.[g.name] !== undefined && check(g, v.trees[g.name]));
2839
+ }
2840
+ }
2806
2841
  return v;
2807
2842
  };
2808
2843
  const asTerm = (t) => (t instanceof Term ? t : lit(t));
@@ -50,5 +50,8 @@ export type Verdict = {
50
50
  kind: 'unknown';
51
51
  point?: Record<string, number>;
52
52
  };
53
+ export declare const work: {
54
+ n: number;
55
+ };
53
56
  export declare function solve(constraints: Constraint[], maxRows?: number): Verdict;
54
57
  export declare function refuteFast(constraints: Constraint[], maxRows?: number): Verdict | undefined;
@@ -56,7 +56,10 @@ const gcd = (a, b) => (b === 0 ? Math.abs(a) : gcd(b, a % b));
56
56
  const vars = (lin) => Object.keys(lin).filter(key => key !== '1');
57
57
  // maxRows: elimination can square the rows each round; past this many live rows, or a round that would make more than
58
58
  // five times as many before merging them, it gives up with `unknown`.
59
+ // The rows the solver has read and made, which a counted search reads as its clock (kernel/prove.ts's `counted`).
60
+ export const work = { n: 0 };
59
61
  export function solve(constraints, maxRows = 20_000) {
62
+ work.n += constraints.length;
60
63
  const steps = [];
61
64
  const lins = [];
62
65
  const push = (step, lin) => (steps.push(step), lins.push(lin), lins.length - 1);
@@ -173,6 +176,7 @@ export function solve(constraints, maxRows = 20_000) {
173
176
  break;
174
177
  if (choice.pos.length * choice.neg.length > 5 * maxRows)
175
178
  return { kind: 'unknown' };
179
+ work.n += choice.pos.length * choice.neg.length;
176
180
  exact &&= choice.isExact;
177
181
  eliminated.push({ x: choice.x, rows: [...choice.pos, ...choice.neg] });
178
182
  const next = rows.filter(r => (lins[r][choice.x] ?? 0) === 0);
@@ -295,6 +299,7 @@ export function refuteFast(constraints, maxRows = 2_000) {
295
299
  z[j] -= T[r][j];
296
300
  const eps = 1e-9;
297
301
  for (let iter = 0; iter < 50 * (R + m); iter++) {
302
+ work.n += 1 + ((R * cols) >> 8);
298
303
  // Bland's rule: the first column with a negative reduced cost enters (no cycling on degenerate systems).
299
304
  let enter = -1;
300
305
  for (let j = 0; j < cols; j++)
@@ -0,0 +1,30 @@
1
+ import type { C, E } from './machine.js';
2
+ import { type Lin } from './solver.js';
3
+ import { type Goal, type Path, type Subst, type Tree } from '../kernel/kernel.js';
4
+ export type Rename = {
5
+ s: Subst;
6
+ cells?: Record<string, {
7
+ to: string;
8
+ at: E;
9
+ }>;
10
+ };
11
+ export type Renamer = (lin: Lin, wp: Path) => Lin | undefined;
12
+ export declare function renamer(r: Rename): Renamer;
13
+ export type TStats = {
14
+ leaves: number;
15
+ regrown: number;
16
+ splits: number;
17
+ carried: number;
18
+ uncarried: number;
19
+ cached: number;
20
+ };
21
+ export declare const tstats: () => TStats;
22
+ export declare function translate(part: Goal, whole: Goal, tree: Tree, ren0: Renamer, st: TStats, until: number, pp?: Path, wp?: Path, swaps?: [string, string][]): Tree | undefined;
23
+ export type Reads = {
24
+ k: E;
25
+ w: E;
26
+ }[];
27
+ export declare function readsFirst(whole: Goal, reads: Reads, ren: Renamer, inner: (wp: Path, swaps: [string, string][]) => Tree | undefined, st: TStats, until: number, wp?: Path, swaps?: [string, string][]): Tree | undefined;
28
+ export declare function decide(whole: Goal, conds: C[], inner: (wp: Path) => Tree | undefined, st: TStats, until: number, wp?: Path): Tree | undefined;
29
+ export declare function close(g: Goal, wp: Path, st: TStats, until: number): Tree | undefined;
30
+ export declare function carry(g: Goal, nFacts: number, nUses: number, st: TStats, until: number): Tree | undefined;