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.
@@ -1,6 +1,6 @@
1
- import { add, k, refuteFast, scale, solve, sub, v } from "../core/solver.js";
1
+ import { add, k, refuteFast, scale, solve, sub, v, work } from "../core/solver.js";
2
2
  import { checkRefutation } from "../core/check.js";
3
- import { crossCyclic, cyclicMachine, cyclicOn, machineNodes, proveCyclic } from "../core/cyclic.js";
3
+ import { allowNode, allowTranslated, certCyclic, certOn, certSearch, closingInsts, crossCyclic, cyclicOn, cycLog, hasQuantified, machineNodes, proveCyclic, reqTS, searchedStepNodes, skMachine, timedKind, withWitness, translatedProof, uncovered, uncoveredWhy } from "../core/cyclic.js";
4
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";
5
5
  // The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
6
6
  function openE(e, path, out) {
@@ -193,6 +193,28 @@ export const cellProducts = { on: false };
193
193
  // A deadline every goal grown inside `withDeadline` keeps, beside its own budget: a search that proves many things (core/
194
194
  // infer.ts, core/discover.ts, the search proveRule falls back on) bounds them all; a goal past it is left `unknown`.
195
195
  let deadline = Infinity;
196
+ // The clock every budget here reads (grow's `until`, withDeadline's; core/cyclic.ts's and core/data.ts's searches): wall
197
+ // time, or within `counted` the work the prover has done (the solver's rows read and made, each node's rows), `perMs`
198
+ // units a millisecond, so that what a search's budget lets it try, and so its verdict, doesn't depend on the machine's
199
+ // speed. A counted search inside another reads the outer's clock.
200
+ let rate = 0;
201
+ export const clock = () => (rate ? work.n / rate : performance.now());
202
+ export function counted(perMs, f) {
203
+ if (rate)
204
+ return f();
205
+ const outer = deadline;
206
+ rate = perMs;
207
+ deadline = Infinity;
208
+ try {
209
+ return f();
210
+ }
211
+ finally {
212
+ rate = 0;
213
+ deadline = outer;
214
+ }
215
+ }
216
+ // A budget's end, within the deadline (a translation's own bound, core/cyclic.ts's, kept by the export's GDP_CERT_MS).
217
+ export const withinDeadline = (until) => Math.min(until, deadline);
196
218
  export function withDeadline(until, f) {
197
219
  const outer = deadline;
198
220
  deadline = Math.min(outer, until);
@@ -205,7 +227,7 @@ export function withDeadline(until, f) {
205
227
  }
206
228
  // `units: false`: the goal's own order, no units first (a caller after a model, not a proof: a run guided by the
207
229
  // prover's witnesses, say, whose models units would steer).
208
- export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
230
+ export function grow(goal, path = new Map(), budget = { nodes: 0, until: clock() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
209
231
  if (deadline < (budget.until ?? Infinity))
210
232
  budget.until = deadline;
211
233
  if (budget.units === false && unitsNow) {
@@ -263,11 +285,12 @@ function growPlain(goal, path, budget) {
263
285
  return { stuck: 'unknown', path, why: 'cap' };
264
286
  if (profile.on)
265
287
  profile.nodes++;
266
- if (++budget.nodes > 200_000 || (budget.until !== undefined && performance.now() > budget.until))
288
+ if (++budget.nodes > 200_000 || (budget.until !== undefined && clock() > budget.until))
267
289
  return { stuck: 'unknown', path, why: outOfBudget(goal, budget.nodes > 200_000 ? 'nodes' : 'time', path.size) };
268
290
  // Rows that read a term column only once a cut has put one on the path; before, the rows a choice-free search uses
269
291
  // (what it saw before term columns), renumbered to the whole leaf's when a leaf is found.
270
292
  const full = timed(() => rowsAt(goal, path), ms => { profile.rowsMs += ms; });
293
+ work.n += 1 + full.length;
271
294
  const columns = [...path.values()].some(a => Object.keys(a.lin).some(key => key.startsWith('⟦')));
272
295
  const idx = full.flatMap((r, i) => (columns || !Object.keys(r.lin).some(key => key.startsWith('⟦')) ? [i] : []));
273
296
  const rows = idx.map(i => full[i]), at = (i) => (i < rows.length ? idx[i] : full.length + i - rows.length);
@@ -1362,15 +1385,56 @@ function proveLawsAsGiven(m, laws) {
1362
1385
  focus: [...(law.forall ? [`${law.forall}#`] : []), ...(law.keys ?? [])],
1363
1386
  });
1364
1387
  });
1365
- const t0 = performance.now(), out = proveThinned(m, laws, { ...instances }), usualMs = performance.now() - t0;
1388
+ // (by default a covered machine's certificate is its cyclic proof, core/cyclic.ts's certCyclic, the usual trees its
1389
+ // input, not checked again; where none is made, the usual trees are checked and the proof counted uncovered)
1390
+ // (what no instance covers, read only where a cyclic mode is on: the usual route's own proofs, inferLaws' trial ones
1391
+ // among them, do nothing more than before)
1392
+ // A machine with a history: its laws are its plain machine's (historyMachine; the record goals leaf goals beside them).
1393
+ const modes = cyclicOn() || certOn(), hist = modes && m.history ? historyMachine(m) : undefined, mc = hist ? hist.machine : m;
1394
+ const why = modes ? uncoveredWhy(mc, laws) : undefined, cert = certOn() && !why;
1395
+ usualCheck.on = !cert;
1396
+ const t0 = performance.now(), out = (() => { try {
1397
+ return proveThinned(m, laws, { ...instances });
1398
+ }
1399
+ finally {
1400
+ usualCheck.on = true;
1401
+ } })(), usualMs = performance.now() - t0;
1402
+ if (out.proof)
1403
+ usualProof.last = { m, laws, proof: out.proof };
1404
+ const inst = out.proof?.instances ?? instances;
1405
+ const searched = () => { const { facts, specs } = machineNodes(mc, laws, inst); return proveCyclic(facts, specs); };
1406
+ const translated = out.proof ? ((st) => translatedProof(() => JSON.stringify(['machine', mc, laws, inst]), () => timedKind('instance', 0, () => machineNodes(mc, laws, inst, out.proof, st)), st)) : undefined;
1366
1407
  if (cyclicOn()) {
1367
- if (cyclicMachine(m, laws))
1368
- crossCyclic('machine', m.name, out.proven && out.checked, () => { const { facts, specs } = machineNodes(m, laws, instances); return proveCyclic(facts, specs); }, usualMs);
1408
+ const usual = out.proven && out.checked;
1409
+ // (a history: its plain machine's cyclic proof, and its record goals as the leaf goals they are; a machine with flags
1410
+ // that hold at every key the usual route doesn't prove: not searched, as before 0.19.0, a search there costs seconds)
1411
+ const records = !hist || hist.goals.every(g => { const tr = out.proof?.trees[g.name] ?? (() => { const o = grow(g); return 'tree' in o ? o.tree : undefined; })(); return tr !== undefined && check(g, tr); });
1412
+ const recordFails = () => ({ proven: false, checked: false, node: m.name, goal: 'a record goal', stuck: 'not proven' });
1413
+ if (!why && (usual || !hasQuantified(mc)))
1414
+ crossCyclic('machine', m.name, usual, records ? searched : recordFails, usualMs, records ? translated : undefined);
1369
1415
  else
1370
- console.error(`cyclic: ${JSON.stringify({ kind: 'machine', name: m.name, usual: out.proven && out.checked, uncovered: true })}`);
1416
+ cycLog({ kind: 'machine', name: m.name, usual, uncovered: true, why: why ?? 'a flag at every key the usual route does not prove' });
1371
1417
  }
1418
+ if (cert && out.proven && out.proof) {
1419
+ const c = certCyclic('machine', m.name, translated, searched, usualMs);
1420
+ if (c.checked) {
1421
+ // (a history's record goals checked as they are: leaf goals)
1422
+ out.checked = !hist || hist.goals.every(g => out.proof.trees[g.name] !== undefined && check(g, out.proof.trees[g.name]));
1423
+ Object.assign(out.proof, { cyclic: c.proof });
1424
+ }
1425
+ else {
1426
+ out.checked = checkLaws(m, laws, out.proof);
1427
+ uncovered('machine', m.name, c.why);
1428
+ }
1429
+ }
1430
+ else if (certOn() && why && out.proven)
1431
+ cycLog({ kind: 'machine', name: m.name, cert: true, uncovered: true, why });
1372
1432
  return out;
1373
1433
  }
1434
+ // Whether the usual proof's trees are checked (off while GDP_CERT=cyclic makes the certificate a cyclic proof).
1435
+ export const usualCheck = { on: true };
1436
+ // The last machine proof made (core/dsl.ts's recurrences read their machine's, to translate it: core/cyclic.ts).
1437
+ export const usualProof = {};
1374
1438
  // Each law with its instances thinned (`triggered`) where that proves it, else with all of them.
1375
1439
  function proveThinned(m, laws, instances) {
1376
1440
  const trees = {}, fulls = { ...instances };
@@ -1416,7 +1480,7 @@ function proveThinned(m, laws, instances) {
1416
1480
  leaves += countLeaves(out.tree);
1417
1481
  }
1418
1482
  const proof = { instances, trees };
1419
- return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
1483
+ return { proven: true, nodes, leaves, checked: !usualCheck.on || checkLaws(m, laws, proof), proof };
1420
1484
  }
1421
1485
  // Every goal of the laws with these instances.
1422
1486
  function proveWith(m, laws, instances) {
@@ -1431,7 +1495,7 @@ function proveWith(m, laws, instances) {
1431
1495
  leaves += countLeaves(out.tree);
1432
1496
  }
1433
1497
  const proof = { instances, trees };
1434
- return { proven: true, nodes, leaves, checked: checkLaws(m, laws, proof), proof };
1498
+ return { proven: true, nodes, leaves, checked: !usualCheck.on || checkLaws(m, laws, proof), proof };
1435
1499
  }
1436
1500
  export function proveStepLaws(m, given, stepLaws) {
1437
1501
  const base = proveLaws(m, given);
@@ -1451,6 +1515,7 @@ export function proveStepLaws(m, given, stepLaws) {
1451
1515
  function proveStepLawsAsGiven(m, laws, stepLaws, base) {
1452
1516
  if (!base.proof)
1453
1517
  return { proven: false, nodes: base.nodes, leaves: base.leaves, checked: false };
1518
+ const t0 = performance.now();
1454
1519
  const plain = m.history ? historyMachine(m).machine : m;
1455
1520
  // Each step law its own instances, from its own goal: the keys the step and the laws read, and its own (a k-step
1456
1521
  // law's later frames read keys no other step law has); where positions shift or a law is about several keys, the
@@ -1505,12 +1570,58 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
1505
1570
  leaves += countLeaves(out.tree);
1506
1571
  }
1507
1572
  const proof = { laws: base.proof, instances, ...(Object.keys(perStep).length ? { perStep } : {}), trees };
1508
- return { proven: true, nodes, leaves, checked: checkStepLaws(m, laws, stepLaws, proof), proof };
1573
+ // Step laws of a machine the cyclic instances cover (one step, or several over no map) are translated with the laws
1574
+ // (core/cyclic.ts); the rest are counted as no instance's.
1575
+ // (a machine with a history: its plain machine's; a flag at every key through its sk machine; k-step laws over a map
1576
+ // searched, where GDP_CERT_SEARCH=1; where no cyclic proof is made the usual trees, checked above, stay the certificate)
1577
+ const modes = cyclicOn() || certOn(), why = modes ? uncoveredWhy(plain, laws) : undefined, one = stepLaws;
1578
+ // Where the cyclic certificate is made below (GDP_CERT, not the cross-check; the laws' certificate checked), the usual
1579
+ // trees are its input, as a machine's laws' are: checkStepLaws (the laws' usual trees again, then the step laws') runs
1580
+ // only where no certificate is made.
1581
+ const byCert = certOn() && !cyclicOn() && !why && one.length > 0 && base.checked;
1582
+ const out = { proven: true, nodes, leaves, checked: byCert || checkStepLaws(m, laws, stepLaws, proof), proof };
1583
+ if (!modes)
1584
+ return out;
1585
+ if (!why && one.length && out.checked) {
1586
+ const all = one.map(sl => { const inst = perStep[sl.name] ?? instances, goal = stepLawObligations(m, laws, inst, sl)[0]; return { sl, inst, goal, tree: trees[goal.name] }; });
1587
+ const translatedOf = (steps) => (st) => translatedProof(() => JSON.stringify(['steps', plain, laws, base.proof.instances, steps.map(x => [x.sl, x.inst])]), reused => timedKind('instance', 0, () => machineNodes(plain, laws, base.proof.instances, { trees: base.proof.trees, steps, lawsMade: reused }, st)), st, () => JSON.stringify(['machine', plain, laws, base.proof.instances]));
1588
+ const usualMs = performance.now() - t0;
1589
+ if (cyclicOn())
1590
+ crossCyclic('step law', m.name, true, undefined, usualMs, translatedOf(all));
1591
+ // (where the step laws' proof together isn't made, each step law's alone: one the translation misses leaves the
1592
+ // others theirs; and the usual trees, unchecked where the certificate was to be the verdict, are checked)
1593
+ const together = certOn() ? certCyclic('step law', m.name, translatedOf(all), undefined, usualMs) : undefined;
1594
+ if (together && !together.checked) {
1595
+ if (byCert)
1596
+ out.checked = checkStepLaws(m, laws, stepLaws, proof);
1597
+ for (const x of all) {
1598
+ const c = all.length > 1 ? certCyclic('step law', m.name, translatedOf([x]), undefined, usualMs) : together;
1599
+ if (!c.checked)
1600
+ uncovered((x.sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, c.why);
1601
+ }
1602
+ }
1603
+ }
1604
+ // (the rest: k steps over a map a request carries, or a machine no instance covers)
1605
+ const rest = stepLaws.filter(sl => why || !one.includes(sl)), restWhy = (sl) => why ?? `${sl.steps} steps over a map a request carries`;
1606
+ if (certOn() && certSearch() && out.checked && rest.length && !why) {
1607
+ // The rest searched: the laws' nodes (searched), each step law's node (core/cyclic.ts's stepLawNodes).
1608
+ const searchedSteps = () => {
1609
+ const b = machineNodes(plain, laws, base.proof.instances);
1610
+ return proveCyclic(b.facts, [...b.specs, ...searchedStepNodes(plain, laws, rest.map(sl => ({ law: sl, instances: perStep[sl.name] ?? instances })), b.facts, b.specs)]);
1611
+ };
1612
+ const c = certCyclic('step law', m.name, undefined, searchedSteps, performance.now() - t0);
1613
+ for (const sl of c.checked ? [] : rest)
1614
+ uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, `${restWhy(sl)}: ${c.why}`);
1615
+ }
1616
+ else if (cyclicOn() || certOn())
1617
+ for (const sl of rest)
1618
+ uncovered((sl.steps ?? 1) > 1 ? 'k-step law' : 'step law', m.name, restWhy(sl));
1619
+ return out;
1509
1620
  }
1510
1621
  // Progress (kernel.ts's progressObligations): the laws proven first, then each goal, the every-key laws assumed at every
1511
1622
  // key the goals read a map at.
1512
1623
  export function proveProgress(m, laws, p) {
1513
- const base = proveLaws(m, laws);
1624
+ const t0 = performance.now(), base = proveLaws(m, laws);
1514
1625
  if (!base.proven || !base.checked)
1515
1626
  return { proven: false, nodes: base.nodes, leaves: base.leaves, ...(base.failed ? { failed: base.failed } : {}), checked: false };
1516
1627
  const ls = base.laws ?? laws;
@@ -1525,12 +1636,48 @@ export function proveProgress(m, laws, p) {
1525
1636
  nodes += out.nodes;
1526
1637
  leaves += countLeaves(out.tree);
1527
1638
  }
1528
- return { proven: true, nodes, leaves, checked: progressObligations(m, ls, instances, p).every(g => check(g, trees[g.name])), instances, trees };
1639
+ const checked = progressObligations(m, ls, instances, p).every(g => check(g, trees[g.name]));
1640
+ // (a request allowed whose claim is that alone, over a machine without a history or carried maps: the laws' nodes,
1641
+ // then the allowed goal translated, core/cyclic.ts's allowTranslated, its last node BridgeRules.lean's cycAOk: from
1642
+ // every reachable state, every request meeting the region and its terms is allowed; where GDP_CERT_SEARCH=1 and that
1643
+ // isn't proven, allowNode searched with the witnesses withWitness finds)
1644
+ const alone = JSON.stringify(p.then) === JSON.stringify(trueC) && !m.history && !m.inputMaps?.length && m.assume !== undefined;
1645
+ if (certOn() && alone && checked && base.proof) {
1646
+ const lp = base.proof, q = hasQuantified(m), goal = progressObligations(m, ls, instances, p)[0];
1647
+ const translated = (st) => translatedProof(() => JSON.stringify(['allowed', m, ls, lp.instances, instances, p]), reused => {
1648
+ const b = machineNodes(m, ls, lp.instances, { ...lp, lawsMade: reused }, st);
1649
+ if (b.why)
1650
+ return b;
1651
+ const a = allowTranslated(m, ls, instances, p, { goal, tree: trees[goal.name] }, b.facts, b.specs, `${m.name}: allowed`, st);
1652
+ return a.why ? { facts: b.facts, specs: [], why: a.why } : { facts: b.facts, specs: [...b.specs, ...a.specs] };
1653
+ }, st, () => JSON.stringify(['machine', m, ls, lp.instances]));
1654
+ const allowed = () => {
1655
+ const b = machineNodes(m, ls, lp.instances, lp);
1656
+ if (b.why || (b.specs[0]?.rank.length ?? 2) < 2)
1657
+ return { proven: false, checked: false, node: m.name, goal: 'allowed', stuck: b.why ?? 'laws ranked by time alone' };
1658
+ let last = { proven: false, checked: false, node: m.name, goal: 'allowed', stuck: 'no witness' };
1659
+ const found = withWitness(m, p.request, wit => {
1660
+ const w = { op: 'and', xs: [p.when, reqTS(Object.entries(p.request)), reqTS(wit)] };
1661
+ const spec = allowNode(skMachine(m), ls, instances, w, b.facts, b.specs, `${m.name}: allowed`, q ? closingInsts(m, b.facts, [instances, ...Object.values(lp.instances)], [{ op: 'var', name: 't$' }], ls) : []);
1662
+ last = proveCyclic(b.facts, [...b.specs, spec]);
1663
+ return last.proven && last.checked ? last : undefined;
1664
+ });
1665
+ return found?.out ?? last;
1666
+ };
1667
+ const c = certCyclic('progress', m.name, translated, certSearch() ? allowed : undefined, performance.now() - t0);
1668
+ if (!c.checked)
1669
+ uncovered('progress', m.name, `a request allowed: ${c.why}`);
1670
+ }
1671
+ else if (cyclicOn() || certOn())
1672
+ uncovered('progress', m.name, alone ? 'a request allowed' : 'a request allowed and a claim after it');
1673
+ return { proven: true, nodes, leaves, checked, instances, trees };
1529
1674
  }
1530
1675
  // Liveness (kernel.ts's livenessParts): its step laws, proven together. The claim needs no more (a run takes the
1531
1676
  // request, so it is allowed); but a request never allowed would leave the claim about no run, so where a helpful request
1532
1677
  // names every input a client sends and carries no maps, it is proven allowed in its region too.
1533
1678
  export function proveLiveness(m, laws, l) {
1679
+ if (cyclicOn() || certOn())
1680
+ uncovered('liveness', m.name, 'a claim about runs that never end');
1534
1681
  const { stay, cover, helps } = livenessParts(m, l);
1535
1682
  const a = proveStepLaws(m, laws, [stay, cover, ...helps]);
1536
1683
  if (!a.proven || !a.checked)
@@ -16,8 +16,9 @@ export declare function leaves(t: Tree, path: Path, visit: (t: Tree & {
16
16
  products?: [number, number][];
17
17
  squares?: Lin[];
18
18
  }, path: Path) => void): void;
19
- export declare function encode(ex: Exported, tamper?: boolean, dropStep?: boolean, globalVars?: string[], globalMaps?: string[]): {
19
+ export declare function encode(ex: Exported, tamper?: boolean, dropStep?: boolean, globalVars?: string[], globalMaps?: string[], slim?: boolean): {
20
20
  tab: string;
21
+ tabCols: string[];
21
22
  hyps: string;
22
23
  tree: string;
23
24
  cols: string[];
@@ -101,6 +102,39 @@ export declare function smachineToLean(id: string, given: Machine, laws: Law[],
101
102
  };
102
103
  noFacts?: boolean;
103
104
  }): string;
105
+ export declare const cyclicCerts: () => boolean;
106
+ export declare const certReport: {
107
+ cyclic: string[];
108
+ usual: {
109
+ id: string;
110
+ why: string;
111
+ }[];
112
+ translatedSteps: string[];
113
+ };
114
+ export declare function cycMachineToLean(id: string, given: Machine, laws: Law[], opts?: {
115
+ stepLaws?: StepLaw[];
116
+ liveness?: Liveness[];
117
+ simp?: string[];
118
+ free?: string[];
119
+ ghosts?: string[];
120
+ after?: (ix: (name: string) => number) => string;
121
+ loop?: {
122
+ loop: Loop;
123
+ };
124
+ noFacts?: boolean;
125
+ refined?: {
126
+ impl: Machine;
127
+ spec: Machine;
128
+ wit: Record<string, E>;
129
+ stutter: C;
130
+ specPre: number;
131
+ step: StepLaw;
132
+ };
133
+ }): {
134
+ text: string;
135
+ } | {
136
+ why: string;
137
+ };
104
138
  type Uses = Record<string, {
105
139
  lean?: string;
106
140
  recur?: Recurrence;
@@ -168,6 +202,7 @@ export type LeanItem = {
168
202
  export declare function toLean(items: LeanItem[], opts?: {
169
203
  title?: string;
170
204
  native?: boolean;
205
+ cyclic?: boolean;
171
206
  }): string;
172
207
  export declare function leanProject(file: string, opts?: {
173
208
  name?: string;