gdp-wired 0.3.0 → 0.4.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.
@@ -0,0 +1,14 @@
1
+ import { type Law, type Machine, type Request, type State, type interpret } from './machine.js';
2
+ import type { StepLaw } from '../kernel/kernel.js';
3
+ export type Watched = Law | StepLaw;
4
+ export declare const isStepLaw: (w: Watched) => w is StepLaw;
5
+ type Runtime = ReturnType<typeof interpret>;
6
+ type Env = Record<string, number>;
7
+ export declare function requestEnv(m: Machine, run: Runtime, params: Env, st: State, input: Request): Env;
8
+ export declare const allowed: (m: Machine, run: Runtime, params: Env, st: State, input: Request) => boolean;
9
+ export declare const keysOf: (bound: number, ...states: State[]) => number[];
10
+ export declare function watch(m: Machine, run: Runtime, w: Watched, params: Env, bound: number): {
11
+ start: (st: State) => boolean;
12
+ step: (_before: State, _input: Request, after: State) => boolean;
13
+ };
14
+ export {};
@@ -0,0 +1,78 @@
1
+ // A law or a step law read off one run as it happens: the concrete side of kernel.ts's goals. The same description
2
+ // (a Law, or a StepLaw with its `steps`, `when` and `then`) is proven about every run by the kernel, searched for a
3
+ // breaking run by Handwired (research/hw/compile.ts), and read here on one run: the examples' random runs (examples/harness.ts),
4
+ // the replay of every counterexample the search finds, and the reference the Handwired search is checked against.
5
+ //
6
+ // A law holds at the start and after every step (at every key, for a law about every key). A step law over k steps holds
7
+ // at every k steps in a row the run takes: each step's `when` read with the state it starts from, its request, queries
8
+ // and flags; `then` with the state before the first step (plain names), the first request and its flags, the j-th
9
+ // request as input@j, and the state and maps after the last step (primed names).
10
+ import { carriedView, evalC, numbersOf } from "./machine.js";
11
+ export const isStepLaw = (w) => 'then' in w;
12
+ // A request's environment: the params, its inputs and queries, and the step's flags decided (as ?name).
13
+ export function requestEnv(m, run, params, st, input) {
14
+ const env = { ...params, ...numbersOf(input), ...run.queries(params, st, input) }, seen = carriedView(m, st, input);
15
+ for (const [name, c] of Object.entries(m.step.flags ?? {}))
16
+ env[`?${name}`] = evalC(c, env, seen, m) ? 1 : 0;
17
+ return env;
18
+ }
19
+ // Whether the machine allows the request: its assumption, read with the queries and flags.
20
+ // A request whose order goes back (with a pruned history) is one the runtimes refuse.
21
+ export const allowed = (m, run, params, st, input) => {
22
+ const order = m.history?.prune?.order;
23
+ if (order !== undefined && st.last !== undefined && input[order] < st.last)
24
+ return false;
25
+ return !m.assume || evalC(m.assume, requestEnv(m, run, params, st, input), carriedView(m, st, input), m);
26
+ };
27
+ // The keys a law about every key is read at: 0 to bound + 1, and every key a map holds.
28
+ export const keysOf = (bound, ...states) => {
29
+ const keys = new Set(Array.from({ length: bound + 2 }, (_, i) => i));
30
+ for (const st of states)
31
+ for (const cells of Object.values(st.maps))
32
+ for (const key of cells.keys())
33
+ keys.add(key);
34
+ return [...keys];
35
+ };
36
+ // Watches one law or step law along one run: `start` with the state at the start, `step` after each step the run takes
37
+ // (the state before, the request, the state after). Each says whether the law still holds.
38
+ export function watch(m, run, w, params, bound) {
39
+ // At every tuple of the keys, one key per name.
40
+ const atKeys = (forall, keys, f, more = []) => {
41
+ const names = forall ? [forall, ...more] : [];
42
+ const go = (i, env) => (i === names.length ? f(env) : keys.every(key => go(i + 1, { ...env, [names[i]]: key })));
43
+ return go(0, {});
44
+ };
45
+ if (!isStepLaw(w)) {
46
+ const holds = (st) => atKeys(w.forall, keysOf(bound, st), extra => evalC(w.holds, { ...params, ...extra }, st, m), w.keys);
47
+ return { start: holds, step: (_before, _input, after) => holds(after) };
48
+ }
49
+ const k = w.steps ?? 1, whens = w.when === undefined ? [] : Array.isArray(w.when) ? w.when : [w.when];
50
+ // `then` reads a primed map's cells and initial value as its own map's.
51
+ const primedMaps = { ...m, maps: { ...m.maps, ...Object.fromEntries(Object.entries(m.maps ?? {}).map(([n, init]) => [`${n}'`, init])) } };
52
+ const window = [];
53
+ return {
54
+ start: (_st) => true,
55
+ step: (before, input, after) => {
56
+ window.push({ before, input, after });
57
+ if (window.length > k)
58
+ window.shift();
59
+ if (window.length < k)
60
+ return true;
61
+ const envs = window.map(t => requestEnv(m, run, params, t.before, t.input));
62
+ if (whens.some((c, j) => !evalC(c, envs[j], carriedView(m, window[j].before, window[j].input), m)))
63
+ return true;
64
+ const env = { ...envs[0] };
65
+ window.slice(1).forEach((t, j) => { for (const i of m.inputs)
66
+ env[`${i}@${j + 2}`] = envs[j + 1][i]; });
67
+ // The first state with the first request's maps; each later request's maps as map@j; the last state primed.
68
+ let first = carriedView(m, window[0].before, window[0].input);
69
+ window.slice(1).forEach((t, j) => { first = carriedView(m, first, t.input, map => `${map}@${j + 2}`); });
70
+ const both = {
71
+ scalars: { ...first.scalars, ...Object.fromEntries(Object.entries(after.scalars).map(([n, v]) => [`${n}'`, v])) },
72
+ maps: { ...first.maps, ...Object.fromEntries(Object.entries(after.maps).map(([n, cells]) => [`${n}'`, cells])) },
73
+ defaults: { ...first.defaults, ...Object.fromEntries(Object.entries(after.defaults ?? {}).map(([n, d]) => [`${n}'`, d])) },
74
+ };
75
+ return atKeys(w.forall, keysOf(bound, first, after), extra => evalC(w.then, { ...env, ...extra }, both, primedMaps), w.keys);
76
+ },
77
+ };
78
+ }
@@ -33,6 +33,7 @@ export type Subst = {
33
33
  export declare function substE(e: E, s: Subst): E;
34
34
  export declare function substC(c: C, s: Subst): C;
35
35
  export type Instances = Record<string, E[]>;
36
+ export declare const assumed: (laws: Law[], instances: Instances) => C[];
36
37
  export declare function scope(m: Machine, laws?: Law[]): void;
37
38
  export declare const keyTerm: (e: E, key: string) => boolean;
38
39
  export declare const plainKey: (t: E | C, key: string) => boolean;
@@ -344,7 +344,7 @@ const laterNames = (m, j) => ({
344
344
  maps: Object.fromEntries((m.inputMaps ?? []).map(map => [map, `${map}@${j}`])),
345
345
  });
346
346
  // The laws assumed where the run is: each plain law, and each every-key law at the keys the proof names.
347
- const assumed = (laws, instances) => laws.flatMap(l => {
347
+ export const assumed = (laws, instances) => laws.flatMap(l => {
348
348
  const keys = instances[l.name] ?? [];
349
349
  if (l.keys?.length) {
350
350
  const names = [l.forall, ...l.keys], n = names.length;
@@ -7,6 +7,7 @@ export type Outcome = {
7
7
  stuck: 'sat' | 'unknown';
8
8
  witness?: Record<string, number>;
9
9
  path: Path;
10
+ why?: string;
10
11
  };
11
12
  export declare function grow(goal: Goal, path?: Path, budget?: {
12
13
  nodes: number;
@@ -20,6 +21,7 @@ export type Report = {
20
21
  goal: string;
21
22
  stuck: string;
22
23
  witness?: Record<string, number>;
24
+ why?: string;
23
25
  };
24
26
  checked: boolean;
25
27
  };
@@ -58,6 +60,7 @@ export declare function proveLiveness(m: Machine, laws: Law[], l: Liveness): Rep
58
60
  };
59
61
  export declare const countLeaves: (t: Tree) => number;
60
62
  export declare function concretize(goal: Goal, bound?: number): Record<string, number> | undefined;
63
+ export declare function factsFor(m: Machine, goal: Goal): C[];
61
64
  export declare function proveBuilt(m: Machine, laws: Law[], build: (inst: Instances) => Goal): {
62
65
  proven: boolean;
63
66
  instances?: Instances;
@@ -1,5 +1,5 @@
1
1
  import { add, k, scale, solve, sub } from "../core/solver.js";
2
- import { canon, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
2
+ import { assumed, canon, check, checkLaws, checkStepLaws, historyMachine, leafConstraints, linear, livenessHelpful, livenessParts, obligations, plainKey, progressObligations, stepLawObligations, trueC, truth, unreachable, withProducts } from "./kernel.js";
3
3
  // The atoms the path leaves open, in evaluation order: deciding any of them makes progress.
4
4
  function openE(e, path, out) {
5
5
  switch (e.op) {
@@ -117,7 +117,7 @@ function parse(text) {
117
117
  // is left `unknown`. A goal with many instances (a law about two keys at its pairs) has that many more rows per leaf.
118
118
  export function grow(goal, path = new Map(), budget = { nodes: 0, until: performance.now() + Math.max(20_000, 1_000 * goal.hyps.length) }) {
119
119
  if (++budget.nodes > 200_000 || (budget.until !== undefined && performance.now() > budget.until))
120
- return { stuck: 'unknown', path };
120
+ return { stuck: 'unknown', path, why: outOfBudget(goal, budget.nodes > 200_000 ? 'nodes' : 'time', path.size) };
121
121
  const rows = leafConstraints(goal, path);
122
122
  const verdict = solve(rows);
123
123
  if (verdict.kind === 'refuted')
@@ -182,6 +182,13 @@ export function grow(goal, path = new Map(), budget = { nodes: 0, until: perform
182
182
  return no;
183
183
  return { tree: { split, yes: yes.tree, no: no.tree }, nodes: budget.nodes };
184
184
  }
185
+ // What ran out, for a goal left unknown: its size, how deep the case splits went, and the two shapes that usually cause it.
186
+ function outOfBudget(goal, limit, depth) {
187
+ const steps = Number(/@(\d+) steps/.exec(goal.name)?.[1] ?? 1);
188
+ return `the prover's ${limit === 'nodes' ? '200,000-node' : 'time'} budget ran out at ${goal.hyps.length} hypotheses, ${depth} case splits deep. ` +
189
+ `Each case a step decides multiplies the cases to refute, so ${steps > 1 ? `a law over ${steps} steps` : 'many interacting cases'} can outgrow it: ` +
190
+ 'state the law over fewer steps (a one-step law and an invariant often give the same), or split it into laws about fewer conditions';
191
+ }
185
192
  // Integer completeness for bounded systems: when elimination is inexact and finds no witness, halve a variable's range
186
193
  // (x <= mid or x >= mid + 1). Each half is a split like any other, so the kernel checks it the same way; a variable
187
194
  // pinned to one value becomes an equality, which the solver substitutes exactly.
@@ -449,7 +456,7 @@ function proveLawsAsGiven(m, laws) {
449
456
  for (const goal of obligations(m, laws, instances)) {
450
457
  const out = grow(goal);
451
458
  if (!('tree' in out))
452
- return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
459
+ return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
453
460
  trees[goal.name] = out.tree;
454
461
  nodes += out.nodes;
455
462
  leaves += countLeaves(out.tree);
@@ -492,7 +499,7 @@ function proveStepLawsAsGiven(m, laws, stepLaws, base) {
492
499
  for (const goal of stepLawObligations(m, laws, perStep[sl.name] ?? instances, sl)) {
493
500
  const out = grow(goal);
494
501
  if (!('tree' in out))
495
- return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
502
+ return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
496
503
  trees[goal.name] = out.tree;
497
504
  nodes += out.nodes;
498
505
  leaves += countLeaves(out.tree);
@@ -516,7 +523,7 @@ export function proveProgress(m, laws, p) {
516
523
  for (const goal of progressObligations(m, ls, instances, p)) {
517
524
  const out = grow(goal);
518
525
  if (!('tree' in out))
519
- return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}) }, checked: false };
526
+ return { proven: false, nodes, leaves, failed: { goal: goal.name, stuck: out.stuck, ...(out.witness ? { witness: out.witness } : {}), ...(out.why ? { why: out.why } : {}) }, checked: false };
520
527
  trees[goal.name] = out.tree;
521
528
  nodes += out.nodes;
522
529
  leaves += countLeaves(out.tree);
@@ -562,7 +569,7 @@ function readKeysC(c) {
562
569
  export const countLeaves = (t) => ('leaf' in t ? 1 : countLeaves(t.yes) + countLeaves(t.no));
563
570
  // A real counterexample for a goal the prover couldn't close (with products, its witness may be abstract): the free
564
571
  // variables over 0..bound, every hypothesis evaluated with true arithmetic. Goals that read maps are left to the
565
- // machine searches (search/), which run whole histories.
572
+ // machine searches (research/search/), which run whole histories.
566
573
  export function concretize(goal, bound = 12) {
567
574
  const names = new Set();
568
575
  let reads = false;
@@ -604,6 +611,14 @@ export function concretize(goal, bound = 12) {
604
611
  }
605
612
  return undefined;
606
613
  }
614
+ // The facts a goal needs, as hypotheses: each fact about every key at the keys the goal reads a function at (and at
615
+ // the keys those facts read, a second round), each plain fact as it is.
616
+ export function factsFor(m, goal) {
617
+ let instances = {};
618
+ for (let round = 0; round < 2; round++)
619
+ instances = withFacts(m, instances, fnKeys(m, [{ ...goal, hyps: [...assumed(m.facts ?? [], instances), ...goal.hyps] }]));
620
+ return assumed(m.facts ?? [], instances);
621
+ }
607
622
  // A goal built from its instances (an exporter's goal, shaped as its Lean twin builds it): the every-key laws, the
608
623
  // quantified flags and the facts assumed at every key it reads a map or function at, then a tree, checked.
609
624
  export function proveBuilt(m, laws, build) {
@@ -57,16 +57,6 @@ export declare function mmachineToLean(id: string, m: Machine, laws: Law[], opts
57
57
  progress?: ProgressCase[];
58
58
  free?: string[];
59
59
  stepLaws?: StepLaw[];
60
- liveness?: Liveness[];
61
- ghosts?: string[];
62
- after?: (ix: (name: string) => number) => string;
63
- refined?: {
64
- impl: Machine;
65
- spec: Machine;
66
- wit: Record<string, E>;
67
- stutter: C;
68
- specPre: number;
69
- };
70
60
  }): string;
71
61
  export declare const hasSets: (m: Machine) => boolean;
72
62
  export declare function smachineToLean(id: string, m: Machine, laws: Law[], opts?: {
@@ -78,6 +68,8 @@ export declare function smachineToLean(id: string, m: Machine, laws: Law[], opts
78
68
  };
79
69
  simp?: string[];
80
70
  free?: string[];
71
+ ghosts?: string[];
72
+ after?: (ix: (name: string) => number) => string;
81
73
  refined?: {
82
74
  impl: Machine;
83
75
  spec: Machine;