gdp-wired 0.3.0 → 0.5.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
+ }
@@ -20,6 +20,10 @@ export type Path = Map<string, {
20
20
  export declare const canon: (lin: Lin) => string;
21
21
  export declare const atomOf: (lin: Lin, holds: boolean) => Constraint;
22
22
  export declare function linear(e: E, path: Path): Lin | undefined;
23
+ export declare const termColumn: (e: E) => string;
24
+ export declare const columnTerm: (name: string) => E | undefined;
25
+ export declare function decided(e: E, path: Path): boolean;
26
+ export declare function termRows(name: string, path: Path): Lin[];
23
27
  export declare function truth(c: C, path: Path): boolean | undefined;
24
28
  export declare function facts(c: C, path: Path, positive?: boolean): Lin[];
25
29
  export declare function leafConstraints(goal: Goal, path: Path): Constraint[];
@@ -33,6 +37,8 @@ export type Subst = {
33
37
  export declare function substE(e: E, s: Subst): E;
34
38
  export declare function substC(c: C, s: Subst): C;
35
39
  export type Instances = Record<string, E[]>;
40
+ export declare const stateName: (n: string, j: number) => string;
41
+ export declare const assumed: (laws: Law[], instances: Instances) => C[];
36
42
  export declare function scope(m: Machine, laws?: Law[]): void;
37
43
  export declare const keyTerm: (e: E, key: string) => boolean;
38
44
  export declare const plainKey: (t: E | C, key: string) => boolean;
@@ -103,6 +109,7 @@ export declare function checkStepLaws(m: Machine, laws: Law[], stepLaws: StepLaw
103
109
  perStep?: Record<string, Instances>;
104
110
  trees: Record<string, Tree>;
105
111
  }): boolean;
112
+ export declare const stepZone: (m: Machine, k: number) => string[];
106
113
  export type Fold = {
107
114
  name: string;
108
115
  params: string[];
@@ -35,11 +35,24 @@ function decide(lin, path) {
35
35
  const flipped = path.get(canon(add(k(1), scale(lin, -1))))?.holds;
36
36
  return flipped === undefined ? undefined : !flipped;
37
37
  }
38
- // A term's linear form under the path, or undefined if the path leaves one of its choices open.
39
- // Reads become cells, named by map and key: `M[key]`.
38
+ // A term's linear form under the path. Reads become cells, named by map and key: `M[key]`; a choice the path leaves
39
+ // open (∸, min, max, if) becomes a column of its own, `⟦n⟧`, standing for the term's value (see termRows).
40
+ // Remembered per path and term (a path is never changed once built; a new decision is a new path), so a term shared
41
+ // across hypotheses, or nested in a k-step run's later states, is read once per path.
42
+ const linearMemo = new WeakMap();
40
43
  export function linear(e, path) {
44
+ let memo = linearMemo.get(path);
45
+ if (!memo)
46
+ linearMemo.set(path, (memo = new WeakMap()));
47
+ if (memo.has(e))
48
+ return memo.get(e);
49
+ const out = linearOf(e, path);
50
+ memo.set(e, out);
51
+ return out;
52
+ }
53
+ function linearOf(e, path) {
41
54
  switch (e.op) {
42
- case 'var': return e.name === '1' || /[*[\],:]/.test(e.name) ? undefined : v(e.name); // those characters build cells and monomials
55
+ case 'var': return e.name === '1' || /[*[\],:⟦]/.test(e.name) ? undefined : v(e.name); // those characters build cells, monomials and term columns
43
56
  case 'const': return k(e.value);
44
57
  case 'mul': {
45
58
  const a = linear(e.a, path);
@@ -61,14 +74,14 @@ export function linear(e, path) {
61
74
  return undefined;
62
75
  const aLeB = decide(sub(a, b), path), bLeA = decide(sub(b, a), path);
63
76
  if (e.op === 'sub')
64
- return bLeA === true ? sub(a, b) : bLeA === false ? k(0) : undefined;
77
+ return bLeA === true ? sub(a, b) : bLeA === false ? k(0) : v(termColumn(e));
65
78
  if (aLeB === undefined)
66
- return undefined;
79
+ return v(termColumn(e));
67
80
  return (e.op === 'min') === aLeB ? a : b;
68
81
  }
69
82
  case 'if': {
70
83
  const c = truth(e.c, path);
71
- return c === undefined ? undefined : linear(c ? e.then : e.else, path);
84
+ return c === undefined ? v(termColumn(e)) : linear(c ? e.then : e.else, path);
72
85
  }
73
86
  case 'get': {
74
87
  const key = linear(e.key, path);
@@ -76,6 +89,61 @@ export function linear(e, path) {
76
89
  }
77
90
  }
78
91
  }
92
+ // --- term columns -------------------------------------------------------------------------------------------------------
93
+ // A choice the path leaves open is a column ⟦n⟧ for the n-th term met (one table per process: the name only has to be
94
+ // the same for the same term). A leaf reads it through rows that hold whichever way the choice goes (termRows), so a
95
+ // fact about the choice's value can be split on, and proven, before the choice is decided.
96
+ // Terms are hash-consed: a term's id comes from its operator, its fields and its parts' ids, remembered per object, so a
97
+ // deep term (a k-step run nests k steps) is read once, not once per lookup.
98
+ const termIds = new Map(), termTable = [], memo = new WeakMap();
99
+ function termId(t) {
100
+ const had = memo.get(t);
101
+ if (had !== undefined)
102
+ return had;
103
+ const parts = Object.entries(t).sort(([x], [y]) => (x < y ? -1 : x > y ? 1 : 0)).map(([key, val]) => (Array.isArray(val) ? `${key}=[${val.map(x => termId(x)).join(',')}]` : typeof val === 'object' && val !== null ? `${key}=#${termId(val)}` : `${key}=${JSON.stringify(val)}`));
104
+ const key = parts.join(';');
105
+ let id = termIds.get(key);
106
+ if (id === undefined) {
107
+ id = termTable.length;
108
+ termIds.set(key, id);
109
+ termTable.push(t);
110
+ }
111
+ memo.set(t, id);
112
+ return id;
113
+ }
114
+ export const termColumn = (e) => `⟦${termId(e)}⟧`;
115
+ export const columnTerm = (name) => { const m = /^⟦(\d+)⟧$/.exec(name); return m ? termTable[Number(m[1])] : undefined; };
116
+ // Whether the path decides a choice's top (an if's condition, which side of min, max or ∸).
117
+ export function decided(e, path) {
118
+ if (e.op === 'if')
119
+ return truth(e.c, path) !== undefined;
120
+ if (e.op !== 'sub' && e.op !== 'min' && e.op !== 'max')
121
+ return true;
122
+ const a = linear(e.a, path), b = linear(e.b, path);
123
+ return !!a && !!b && decide(e.op === 'sub' ? sub(b, a) : sub(a, b), path) !== undefined;
124
+ }
125
+ // The rows a term column brings: its bounds, true whichever way the choice goes (min(a, b) ≤ a and ≤ b; max(a, b) ≥ a
126
+ // and ≥ b; a ∸ b ≥ a − b and ≤ a), and, once the path decides it, its equality to the branch taken.
127
+ export function termRows(name, path) {
128
+ const e = columnTerm(name), col = v(name), out = [];
129
+ if (e.op === 'min' || e.op === 'max' || e.op === 'sub') {
130
+ const a = linear(e.a, path), b = linear(e.b, path);
131
+ if (a && b) {
132
+ if (e.op === 'min')
133
+ out.push(sub(col, a), sub(col, b));
134
+ if (e.op === 'max')
135
+ out.push(sub(a, col), sub(b, col));
136
+ if (e.op === 'sub')
137
+ out.push(sub(sub(a, b), col), sub(col, a));
138
+ }
139
+ }
140
+ if (decided(e, path)) {
141
+ const r = linear(e, path);
142
+ if (r)
143
+ out.push(sub(col, r), sub(r, col));
144
+ }
145
+ return out;
146
+ }
79
147
  // Products: multiply out, each monomial (variables joined by '*', sorted) one more natural variable. Forgetting how
80
148
  // monomials relate (x*x to x, say) only weakens what a leaf may use, so it stays sound; distributing is what proves
81
149
  // spend <= m·count over a node from the same law at its children. Map reads can't be factors.
@@ -83,8 +151,8 @@ function times(a, b) {
83
151
  const out = [];
84
152
  for (const [p, cp] of Object.entries(a))
85
153
  for (const [q, cq] of Object.entries(b)) {
86
- if (p.includes('[') || q.includes('['))
87
- return undefined;
154
+ if (p.includes('[') || q.includes('[') || p.includes('⟦') || q.includes('⟦'))
155
+ return undefined; // factors are variables (and monomials of them)
88
156
  const factors = [...(p === '1' ? [] : p.split('*')), ...(q === '1' ? [] : q.split('*'))].sort();
89
157
  out.push({ [factors.length ? factors.join('*') : '1']: cp * cq });
90
158
  }
@@ -172,6 +240,23 @@ export function leafConstraints(goal, path) {
172
240
  if (decide(d, path) === true && decide(scale(d, -1), path) === true)
173
241
  rows.push({ lin: sub(v(p.name), v(q.name)), label: '' }, { lin: sub(v(q.name), v(p.name)), label: '' });
174
242
  }
243
+ // Each term column's rows, and the columns those rows bring in turn.
244
+ const seen = new Set(), todo = [...names].filter(n => n.startsWith('⟦'));
245
+ while (todo.length) {
246
+ const name = todo.pop();
247
+ if (seen.has(name))
248
+ continue;
249
+ seen.add(name);
250
+ for (const lin of termRows(name, path)) {
251
+ rows.push({ lin, label: '' });
252
+ for (const key of Object.keys(lin))
253
+ if (key !== '1') {
254
+ names.add(key);
255
+ if (key.startsWith('⟦') && !seen.has(key))
256
+ todo.push(key);
257
+ }
258
+ }
259
+ }
175
260
  for (const name of names)
176
261
  rows.push({ lin: { [name]: -1 }, label: '' });
177
262
  return rows;
@@ -300,6 +385,14 @@ const step = (r, u) => {
300
385
  flags: at.flags,
301
386
  } };
302
387
  };
388
+ // The state after a step, named: each state variable a fresh name n~j equal to its term there, which later frames read,
389
+ // so a run of k steps is k small frames rather than a term nesting each state in the next (which grows as 3^k when a
390
+ // step reads its state thrice). The names are new to the goal; each equation holds of the run that took the steps.
391
+ const nameState = (r, state, j) => ({
392
+ hyps: [...r.hyps, ...state.map(n => ({ op: '=', a: { op: 'var', name: stateName(n, j) }, b: r.at.vars[n] ?? { op: 'var', name: n } }))],
393
+ at: { ...r.at, vars: { ...r.at.vars, ...Object.fromEntries(state.map(n => [n, { op: 'var', name: stateName(n, j) }])) } },
394
+ });
395
+ export const stateName = (n, j) => `${n}~${j}`;
303
396
  // The conclusion read at the run's end: plain names at the last frame.
304
397
  const atEnd = (r, c) => substC(c, r.at);
305
398
  // The conclusion read across the run: plain names at the first frame, primed names (s', and M'[k] as a read of the map
@@ -344,7 +437,7 @@ const laterNames = (m, j) => ({
344
437
  maps: Object.fromEntries((m.inputMaps ?? []).map(map => [map, `${map}@${j}`])),
345
438
  });
346
439
  // 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 => {
440
+ export const assumed = (laws, instances) => laws.flatMap(l => {
348
441
  const keys = instances[l.name] ?? [];
349
442
  if (l.keys?.length) {
350
443
  const names = [l.forall, ...l.keys], n = names.length;
@@ -799,8 +892,9 @@ export function stepLawObligations(machine, laws, instances, sl) {
799
892
  const primes = [...state, ...maps].map(n => `${n}'`);
800
893
  const laters = Array.from({ length: k - 1 }, (_, i) => laterNames(m, i + 2));
801
894
  const later = laters.flatMap(l => Object.values(l.vars).map(e => e.name)), laterMaps = laters.flatMap(l => Object.values(l.maps));
895
+ const names = Array.from({ length: k - 1 }, (_, i) => state.map(n => stateName(n, i + 1))).flat();
802
896
  const taken = [...m.params, ...m.inputs, ...state, ...maps, ...carried];
803
- for (const p of [...primes, ...later, ...laterMaps])
897
+ for (const p of [...primes, ...later, ...laterMaps, ...names])
804
898
  if (taken.includes(p))
805
899
  throw new Error(`${m.name}: ${p} is a name of the machine and a name a step law reads`);
806
900
  for (const key of [sl.forall, ...(sl.keys ?? [])])
@@ -822,14 +916,30 @@ export function stepLawObligations(machine, laws, instances, sl) {
822
916
  if (j > 1)
823
917
  run = reading(run, laters[j - 2].vars, laters[j - 2].maps);
824
918
  run = step(holds(run, [...assumed(laws, instances), ...assume, ...(whens[j - 1] ? [inlineFlags(m, whens[j - 1])] : []), ...closing(m, instances)]), u);
919
+ if (j < k)
920
+ run = nameState(run, state, j);
825
921
  }
826
922
  return [goal(k === 1 ? `${sl.name} @step law` : `${sl.name} @${k} steps`, run, across(run, sl.then, state, maps, { inputs: {}, flags: flagsOf(m) }))];
827
923
  }
828
924
  // The laws' proof and every step law's tree: all must check.
829
925
  // `perStep`: a step law's own instances, where it has them (else `instances`).
926
+ // Each frame's hypotheses read no name a later step's request is read by: the condition the Lean twin's proof needs
927
+ // (Sets.lean's checkSKStep, `avoids (szone s sl)`; plain flags are inlined here, so their names never occur), checked
928
+ // here alike.
830
929
  export function checkStepLaws(m, laws, stepLaws, proof) {
831
- return checkLaws(m, laws, proof.laws) && stepLaws.every(sl => stepLawObligations(m, laws, proof.perStep?.[sl.name] ?? proof.instances, sl).every(g => proof.trees[g.name] !== undefined && check(g, proof.trees[g.name])));
930
+ return checkLaws(m, laws, proof.laws) && stepLaws.every(sl => {
931
+ const inst = proof.perStep?.[sl.name] ?? proof.instances, plain = m.history ? historyMachine(m).machine : m;
932
+ const zone = stepZone(plain, sl.steps ?? 1);
933
+ return [...assumed(laws, inst), ...assumed(plain.facts ?? [], inst), ...closing(plain, inst)].every(c => !mentions(c, zone))
934
+ && stepLawObligations(m, laws, inst, sl).every(g => proof.trees[g.name] !== undefined && check(g, proof.trees[g.name]));
935
+ });
832
936
  }
937
+ // The names a k-step law's run introduces past its first frame: each later step's inputs, quantified flags and their
938
+ // witnesses, and the state after each step but the last (stateName).
939
+ export const stepZone = (m, k) => [
940
+ ...Array.from({ length: k - 1 }, (_, i) => Object.values(laterNames(m, i + 2).vars).map(e => e.name)).flat(),
941
+ ...Array.from({ length: k - 1 }, (_, i) => Object.keys(m.state).map(n => stateName(n, i + 1))).flat(),
942
+ ];
833
943
  export function foldObligations(f, laws) {
834
944
  if (![...Object.values(f.leaf), ...Object.values(f.node)].every(natural) || !laws.every(law => !law.forall && !law.keys?.length && naturalC(law.holds) && !mentions(law.holds, f.inputs)))
835
945
  throw new Error('outside the kernel');
@@ -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;