gdp-wired 0.0.0-stage → 0.2.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,182 @@
1
+ // Linear arithmetic over the integers, inside the library. A conjunction of constraints `lin <= 0` is decided by
2
+ // Fourier–Motzkin elimination with integer cuts, and every answer carries evidence that a small checker verifies:
3
+ // refuted a derivation: input rows, positive combinations of two rows, and cuts (divide a row's variable
4
+ // coefficients by their common divisor g and round the constant up), ending in `positive <= 0`
5
+ // sat an integer witness, checked by evaluating every input constraint at it
6
+ // unknown elimination was inexact for the integers and no witness was found (never a wrong answer)
7
+ // Elimination of x is exact for the integers when every pair it combines has a coefficient ±1 on x (the Omega test's
8
+ // exact shadow). Then a rational solution of the rest extends to an integer x, so back-substitution finds a witness.
9
+ export const v = (name) => ({ [name]: 1 });
10
+ export const k = (value) => (value === 0 ? {} : { 1: value });
11
+ export function add(...xs) {
12
+ const out = {};
13
+ for (const x of xs)
14
+ for (const [key, value] of Object.entries(x))
15
+ out[key] = (out[key] ?? 0) + value;
16
+ for (const key of Object.keys(out))
17
+ if (out[key] === 0)
18
+ delete out[key];
19
+ return out;
20
+ }
21
+ export const scale = (x, f) => add(Object.fromEntries(Object.entries(x).map(([key, value]) => [key, value * f])));
22
+ export const sub = (a, b) => add(a, scale(b, -1));
23
+ export function show(x) {
24
+ const parts = Object.entries(x).filter(([key]) => key !== '1').map(([key, c]) => (c === 1 ? key : c === -1 ? `-${key}` : `${c}${key}`));
25
+ if ((x['1'] ?? 0) !== 0 || parts.length === 0)
26
+ parts.push(String(x['1'] ?? 0));
27
+ return parts.join(' + ').replace(/\+ -/g, '- ');
28
+ }
29
+ export const le = (a, b, label) => [{ lin: sub(a, b), label: label ?? `${show(a)} <= ${show(b)}` }];
30
+ export const lt = (a, b, label) => [{ lin: add(sub(a, b), k(1)), label: label ?? `${show(a)} < ${show(b)}` }];
31
+ export const eq = (a, b, label) => [...le(a, b, label ?? `${show(a)} = ${show(b)}`), ...le(b, a, label ?? `${show(a)} = ${show(b)}`)];
32
+ export const nat = (...names) => names.flatMap(name => le(k(0), v(name), `${name} >= 0`));
33
+ // not (lin <= 0) over the integers is lin >= 1, i.e. 1 − lin <= 0.
34
+ export const negate = (c) => ({ lin: add(k(1), scale(c.lin, -1)), label: `not (${c.label})` });
35
+ const gcd = (a, b) => (b === 0 ? Math.abs(a) : gcd(b, a % b));
36
+ const vars = (lin) => Object.keys(lin).filter(key => key !== '1');
37
+ // maxRows: elimination can square the rows each round; past this many live rows it gives up with `unknown`.
38
+ export function solve(constraints, maxRows = 20_000) {
39
+ const steps = [];
40
+ const lins = [];
41
+ const push = (step, lin) => (steps.push(step), lins.push(lin), lins.length - 1);
42
+ // A row after its cut, if its variables share a divisor.
43
+ const tighten = (index) => {
44
+ const lin = lins[index];
45
+ const g = vars(lin).reduce((acc, key) => gcd(acc, lin[key]), 0);
46
+ if (g <= 1)
47
+ return index;
48
+ const cut = Object.fromEntries(vars(lin).map(key => [key, lin[key] / g]));
49
+ const constant = Math.ceil((lin['1'] ?? 0) / g);
50
+ return push({ kind: 'cut', of: index, g }, add(cut, k(constant)));
51
+ };
52
+ let rows = constraints.map((c, index) => tighten(push({ kind: 'input', index }, c.lin)));
53
+ const contradiction = () => rows.find(r => vars(lins[r]).length === 0 && (lins[r]['1'] ?? 0) > 0);
54
+ const eliminated = [];
55
+ let exact = true;
56
+ // Equalities first: a pair of rows that are each other's negation, with a variable of coefficient ±1, defines that
57
+ // variable. Substituting it into every other row (a combination with one row of the pair) eliminates it exactly,
58
+ // with |rows| new rows instead of the |pos| × |neg| of a plain elimination.
59
+ for (let changed = true; changed && contradiction() === undefined;) {
60
+ changed = false;
61
+ const key = (lin) => JSON.stringify(Object.entries(lin).sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)));
62
+ const byKey = new Map(rows.map(r => [key(lins[r]), r]));
63
+ for (const r of rows) {
64
+ const neg = byKey.get(key(scale(lins[r], -1)));
65
+ if (neg === undefined || neg === r)
66
+ continue;
67
+ const x = vars(lins[r]).find(name => Math.abs(lins[r][name]) === 1);
68
+ if (x === undefined)
69
+ continue;
70
+ // up: the row of the pair with +x, down: with −x.
71
+ const [up, down] = lins[r][x] === 1 ? [r, neg] : [neg, r];
72
+ const next = [];
73
+ for (const o of rows) {
74
+ if (o === up || o === down)
75
+ continue;
76
+ const c = lins[o][x] ?? 0;
77
+ if (c === 0)
78
+ next.push(o);
79
+ else
80
+ next.push(tighten(push({ kind: 'combine', a: o, b: c > 0 ? down : up, ma: 1, mb: Math.abs(c) }, add(lins[o], scale(lins[c > 0 ? down : up], Math.abs(c))))));
81
+ }
82
+ eliminated.push({ x, rows: [up, down] });
83
+ rows = next;
84
+ changed = true;
85
+ break;
86
+ }
87
+ }
88
+ for (let found = contradiction(); found === undefined; found = contradiction()) {
89
+ const remaining = [...new Set(rows.flatMap(r => vars(lins[r])))];
90
+ if (remaining.length === 0)
91
+ break;
92
+ // Eliminate the variable that makes the fewest new rows, preferring exact eliminations.
93
+ const cost = (x) => {
94
+ const pos = rows.filter(r => (lins[r][x] ?? 0) > 0), neg = rows.filter(r => (lins[r][x] ?? 0) < 0);
95
+ const isExact = pos.every(p => lins[p][x] === 1) || neg.every(n => lins[n][x] === -1);
96
+ return { x, pos, neg, isExact, size: pos.length * neg.length - pos.length - neg.length };
97
+ };
98
+ const choice = remaining.map(cost).sort((a, b) => Number(b.isExact) - Number(a.isExact) || a.size - b.size)[0];
99
+ exact &&= choice.isExact;
100
+ eliminated.push({ x: choice.x, rows: [...choice.pos, ...choice.neg] });
101
+ const next = rows.filter(r => (lins[r][choice.x] ?? 0) === 0);
102
+ for (const p of choice.pos)
103
+ for (const n of choice.neg) {
104
+ const a = lins[p][choice.x], b = -lins[n][choice.x];
105
+ const combined = push({ kind: 'combine', a: p, b: n, ma: b / gcd(a, b), mb: a / gcd(a, b) }, add(scale(lins[p], b / gcd(a, b)), scale(lins[n], a / gcd(a, b))));
106
+ next.push(tighten(combined));
107
+ }
108
+ // Rows with the same variable part: only the tightest (largest constant) matters; rows with no variables that hold
109
+ // say nothing.
110
+ const best = new Map();
111
+ for (const r of next) {
112
+ const lin = lins[r], sig = JSON.stringify(vars(lin).sort().map(key => [key, lin[key]]));
113
+ if (sig === '[]' && (lin['1'] ?? 0) <= 0)
114
+ continue;
115
+ const seen = best.get(sig);
116
+ if (seen === undefined || (lins[seen]['1'] ?? 0) < (lin['1'] ?? 0))
117
+ best.set(sig, r);
118
+ }
119
+ rows = [...best.values()];
120
+ if (rows.length > maxRows)
121
+ return { kind: 'unknown' };
122
+ }
123
+ const found = contradiction();
124
+ if (found !== undefined)
125
+ return { kind: 'refuted', steps: prune(steps, found) };
126
+ // A rational solution the same way (cuts only remove non-integer points, so bounds never cross): where to split.
127
+ const rational = {};
128
+ for (const { x, rows: bounds } of [...eliminated].reverse()) {
129
+ let lower = Number.NEGATIVE_INFINITY, upper = Number.POSITIVE_INFINITY;
130
+ for (const r of bounds) {
131
+ const lin = lins[r], a = lin[x];
132
+ const rest = Object.entries(lin).reduce((sum, [key, c]) => (key === x ? sum : sum + c * (key === '1' ? 1 : (rational[key] ?? 0))), 0);
133
+ if (a > 0)
134
+ upper = Math.min(upper, -rest / a);
135
+ else
136
+ lower = Math.max(lower, -rest / a);
137
+ }
138
+ rational[x] = lower === Number.NEGATIVE_INFINITY ? Math.min(0, upper) : lower;
139
+ }
140
+ // Back-substitution, last eliminated first: each variable takes the least integer its bounds allow.
141
+ const point = {};
142
+ for (const { x, rows: bounds } of eliminated.reverse()) {
143
+ let lower = Number.NEGATIVE_INFINITY, upper = Number.POSITIVE_INFINITY;
144
+ for (const r of bounds) {
145
+ const lin = lins[r], a = lin[x];
146
+ const rest = Object.entries(lin).reduce((sum, [key, c]) => (key === x ? sum : sum + c * (key === '1' ? 1 : (point[key] ?? 0))), 0);
147
+ if (a > 0)
148
+ upper = Math.min(upper, Math.floor(-rest / a));
149
+ else
150
+ lower = Math.max(lower, Math.ceil(-rest / a));
151
+ }
152
+ point[x] = lower === Number.NEGATIVE_INFINITY ? Math.min(0, upper) : lower;
153
+ }
154
+ for (const c of constraints)
155
+ for (const key of vars(c.lin))
156
+ point[key] ??= 0;
157
+ const holds = constraints.every(c => Object.entries(c.lin).reduce((sum, [key, a]) => sum + a * (key === '1' ? 1 : point[key]), 0) <= 0);
158
+ // An exact elimination always back-substitutes to a witness; anything else is reported as unknown, never as sat.
159
+ void exact;
160
+ return holds ? { kind: 'sat', witness: point } : { kind: 'unknown', point: rational };
161
+ }
162
+ // Only the steps the contradiction depends on, renumbered.
163
+ function prune(steps, last) {
164
+ const keep = new Set();
165
+ const visit = (i) => {
166
+ if (keep.has(i))
167
+ return;
168
+ keep.add(i);
169
+ const s = steps[i];
170
+ if (s.kind === 'combine')
171
+ (visit(s.a), visit(s.b));
172
+ if (s.kind === 'cut')
173
+ visit(s.of);
174
+ };
175
+ visit(last);
176
+ const order = [...keep].sort((a, b) => a - b);
177
+ const at = new Map(order.map((old, i) => [old, i]));
178
+ return order.map(i => {
179
+ const s = steps[i];
180
+ return s.kind === 'combine' ? { ...s, a: at.get(s.a), b: at.get(s.b) } : s.kind === 'cut' ? { ...s, of: at.get(s.of) } : s;
181
+ });
182
+ }
@@ -0,0 +1,88 @@
1
+ import type { C, E, Law, Machine } from '../core/machine.js';
2
+ import { type Constraint, type Lin, type Step } from '../core/solver.js';
3
+ export declare function unreachable(value: never): never;
4
+ export type Tree = {
5
+ split: Lin;
6
+ yes: Tree;
7
+ no: Tree;
8
+ } | {
9
+ leaf: Step[];
10
+ products?: [number, number][];
11
+ };
12
+ export type Goal = {
13
+ name: string;
14
+ hyps: C[];
15
+ };
16
+ export type Path = Map<string, {
17
+ lin: Lin;
18
+ holds: boolean;
19
+ }>;
20
+ export declare const canon: (lin: Lin) => string;
21
+ export declare const atomOf: (lin: Lin, holds: boolean) => Constraint;
22
+ export declare function linear(e: E, path: Path): Lin | undefined;
23
+ export declare function truth(c: C, path: Path): boolean | undefined;
24
+ export declare function facts(c: C, path: Path, positive?: boolean): Lin[];
25
+ export declare function leafConstraints(goal: Goal, path: Path): Constraint[];
26
+ export declare function withProducts(rows: Constraint[], pairs: [number, number][]): Constraint[];
27
+ export declare function check(goal: Goal, tree: Tree, path?: Path): boolean;
28
+ type Subst = {
29
+ vars: Record<string, E>;
30
+ maps: Record<string, (key: E) => E>;
31
+ flags: Record<string, C>;
32
+ };
33
+ export declare function substC(c: C, s: Subst): C;
34
+ export type Instances = Record<string, E[]>;
35
+ export declare function scope(m: Machine, laws?: Law[]): void;
36
+ export declare function historyMachine(m: Machine): {
37
+ machine: Machine;
38
+ goals: Goal[];
39
+ };
40
+ export declare function obligations(m: Machine, laws: Law[], instances?: Record<string, Instances>): Goal[];
41
+ export declare function checkLaws(m: Machine, laws: Law[], proof: {
42
+ instances: Record<string, Instances>;
43
+ trees: Record<string, Tree>;
44
+ }): boolean;
45
+ export type Loop = {
46
+ name: string;
47
+ params: string[];
48
+ inputs: string[];
49
+ state: Record<string, E>;
50
+ while: C;
51
+ body: Record<string, E>;
52
+ pre?: C;
53
+ assume?: C;
54
+ };
55
+ export declare function loopObligations(l: Loop, laws: Law[], rank: E | E[]): Goal[];
56
+ export declare function progressObligations(m: Machine, laws: Law[], instances: Instances, p: {
57
+ name: string;
58
+ when: C;
59
+ then: C;
60
+ request: Record<string, E>;
61
+ }): Goal[];
62
+ export type StepLaw = {
63
+ name: string;
64
+ when?: C | C[];
65
+ then: C;
66
+ forall?: string;
67
+ steps?: number;
68
+ };
69
+ export declare function stepLawObligations(machine: Machine, laws: Law[], instances: Instances, sl: StepLaw): Goal[];
70
+ export declare function checkStepLaws(m: Machine, laws: Law[], stepLaws: StepLaw[], proof: {
71
+ laws: {
72
+ instances: Record<string, Instances>;
73
+ trees: Record<string, Tree>;
74
+ };
75
+ instances: Instances;
76
+ trees: Record<string, Tree>;
77
+ }): boolean;
78
+ export type Fold = {
79
+ name: string;
80
+ params: string[];
81
+ inputs: string[];
82
+ leaf: Record<string, E>;
83
+ node: Record<string, E>;
84
+ pre?: C;
85
+ assume?: C;
86
+ };
87
+ export declare function foldObligations(f: Fold, laws: Law[]): Goal[];
88
+ export {};