gdp-wired 0.12.0 → 0.12.2

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.
@@ -100,7 +100,12 @@ export function solve(constraints, maxRows = 20_000) {
100
100
  const neg = byKey.get(negKey(r));
101
101
  if (neg === undefined || neg === r)
102
102
  continue;
103
- const x = vars(lins[r]).find(name => Math.abs(lins[r][name]) === 1);
103
+ let x;
104
+ for (const name in lins[r])
105
+ if (name !== '1' && Math.abs(lins[r][name]) === 1) {
106
+ x = name;
107
+ break;
108
+ }
104
109
  if (x === undefined)
105
110
  continue;
106
111
  // up: the row of the pair with +x, down: with −x.
@@ -139,9 +144,10 @@ export function solve(constraints, maxRows = 20_000) {
139
144
  // Each variable's rows by sign, in one pass (variables in the order they first appear).
140
145
  const index = new Map();
141
146
  for (const r of rows)
142
- for (const [x, c] of Object.entries(lins[r])) {
147
+ for (const x in lins[r]) {
143
148
  if (x === '1')
144
149
  continue;
150
+ const c = lins[r][x];
145
151
  let e = index.get(x);
146
152
  if (!e)
147
153
  index.set(x, (e = { pos: [], neg: [], posUnit: true, negUnit: true }));
@@ -1120,7 +1120,7 @@ export function stepLawObligations(machine, laws, instances, sl) {
1120
1120
  const m = machine.history ? historyMachine(machine).machine : machine;
1121
1121
  scope(m, laws);
1122
1122
  const k = sl.steps ?? 1;
1123
- // Past 64 the cost is the bound (proving grows about as k³: 64 steps in about 17 s on Modal; Lean checks 32 in about 25 s, 64 in about 3 minutes).
1123
+ // Past 64 the cost is the bound (proving grows about as k³: 64 steps in about 5 s on Modal; Lean checks 32 in about 14 s, 64 in about 47 s).
1124
1124
  if (!Number.isInteger(k) || k < 1 || k > 64)
1125
1125
  throw new Error(`step law ${sl.name}: steps is a whole number from 1 to 64`);
1126
1126
  const whens = sl.when === undefined ? [] : Array.isArray(sl.when) ? sl.when : [sl.when];
@@ -210,6 +210,13 @@ function growPlain(goal, path, budget) {
210
210
  const verdict = next && rows.length > 60 ? solveP(rows, 2 * rows.length) : solveP(rows);
211
211
  if (verdict.kind === 'refuted')
212
212
  return { tree: leafOf(verdict.steps), nodes: budget.nodes };
213
+ // Before a split, the rows the plain search sets aside (those reading a term column) by their Farkas rows: an open
214
+ // choice's bounds (min(a, c) ≤ c) often refute the case whichever way the choice goes, so its cases need no split.
215
+ if (next && !columns && full.length > rows.length) {
216
+ const fast = timed(() => refuteFast(full), ms => { profile.solves++; profile.solveMs += ms; });
217
+ if (fast?.kind === 'refuted')
218
+ return { tree: { leaf: fast.steps }, nodes: budget.nodes };
219
+ }
213
220
  let split = next ?? (verdict.kind === 'unknown' ? bisect(rows, path) ?? fractional(verdict.point, path) : undefined);
214
221
  // Products: with monomials about, try every pair of rows multiplied; failing that, split a factor at zero.
215
222
  const monomials = rows.some(r => Object.keys(r.lin).some(key => key.includes('*')));
@@ -405,15 +412,25 @@ function growFocused(goal, path, focus, budget, near = focus) {
405
412
  }
406
413
  // A refutation from the rows near some facts (the rows reachable from their variables in two hops, each row joining the
407
414
  // variables it reads), its inputs renumbered to the whole leaf's. A cut about one step of a long run needs that step's
408
- // rows, not the run's; a cut it misses is only a cut not made (the plain search still follows).
415
+ // rows, not the run's; a cut it misses is only a cut not made (the plain search still follows). A variable most rows
416
+ // read (a param every step reads) joins no rows: through it every step is near every other.
409
417
  function solveNear(rows, seeds) {
418
+ const uses = new Map();
419
+ for (const r of rows)
420
+ for (const key in r.lin)
421
+ if (key !== '1')
422
+ uses.set(key, (uses.get(key) ?? 0) + 1);
423
+ const hub = (key) => (uses.get(key) ?? 0) > Math.max(16, rows.length / 8);
410
424
  const reach = new Set(seeds.flatMap(l => Object.keys(l).filter(key => key !== '1')));
411
- for (let hop = 0; hop < 2; hop++)
425
+ for (let hop = 0; hop < 2; hop++) {
426
+ // One hop: the rows reading what was reached before it (not what this hop adds, or one pass runs along the run).
427
+ const from = new Set(reach);
412
428
  for (const r of rows) {
413
429
  const keys = Object.keys(r.lin).filter(key => key !== '1');
414
- if (keys.some(key => reach.has(key)))
430
+ if (keys.some(key => from.has(key) && !hub(key)))
415
431
  keys.forEach(key => reach.add(key));
416
432
  }
433
+ }
417
434
  const picked = rows.flatMap((r, i) => (Object.keys(r.lin).every(key => key === '1' || reach.has(key)) ? [i] : []));
418
435
  const near = solve(picked.map(i => rows[i]));
419
436
  return near.kind === 'refuted' ? { kind: 'refuted', steps: near.steps.map(st => (st.kind === 'input' ? { ...st, index: picked[st.index] } : st)) } : near;
@@ -137,7 +137,7 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
137
137
  coefs.pop();
138
138
  return coefs;
139
139
  };
140
- // Rows by their nonzero coefficients (Kernel.lean's sp builds the dense row): most of a wide row is zeros.
140
+ // Rows by their nonzero coefficients (Cert.lean's rows hold them so): most of a wide row is zeros.
141
141
  const sparse = (coefs) => list(coefs.flatMap((x, j) => (x === 0 ? [] : [`(${j}, ${int(x)})`])));
142
142
  const row = (lin) => `(sp ${sparse(dense(lin))} ${int(lin['1'] ?? 0)})`;
143
143
  const { e, c } = leanTerms(vars, maps, ex.name);
@@ -173,7 +173,7 @@ export function encode(ex, tamper = false, dropStep = true, globalVars, globalMa
173
173
  const q = Object.fromEntries(Object.entries(from).filter(([key]) => key !== '1').map(([key, v]) => [key, v / s.g]));
174
174
  const cc = Math.ceil((from['1'] ?? 0) / s.g);
175
175
  replay.push({ ...q, 1: cc });
176
- return `.cut ${s.of} ${int(s.g)} (sparseL 0 ${sparse(dense(q))}) ${int(cc)}`;
176
+ return `.cut ${s.of} ${int(s.g)} ${sparse(dense(q))} ${int(cc)}`;
177
177
  });
178
178
  if (tamper && dropStep && leafNo++ === 0)
179
179
  steps = steps.slice(0, -1); // the first leaf loses its last step