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.
- package/README.md +106 -113
- package/dist/core/dsl.d.ts +188 -4
- package/dist/core/dsl.js +733 -52
- package/dist/core/watch.d.ts +14 -0
- package/dist/core/watch.js +78 -0
- package/dist/kernel/kernel.d.ts +1 -0
- package/dist/kernel/kernel.js +1 -1
- package/dist/kernel/prove.d.ts +3 -0
- package/dist/kernel/prove.js +21 -6
- package/dist/lean-kernel/emit.d.ts +2 -10
- package/dist/lean-kernel/emit.js +131 -192
- package/dist/lean-kernel/library.js +4 -4
- package/package.json +2 -2
|
@@ -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
|
+
}
|
package/dist/kernel/kernel.d.ts
CHANGED
|
@@ -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;
|
package/dist/kernel/kernel.js
CHANGED
|
@@ -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;
|
package/dist/kernel/prove.d.ts
CHANGED
|
@@ -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;
|
package/dist/kernel/prove.js
CHANGED
|
@@ -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;
|