gdp-wired 0.12.6 → 0.12.7
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 +1 -1
- package/dist/core/dsl.js +6 -3
- package/dist/core/machine.js +35 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -316,7 +316,7 @@ const soon = retry.law('the next try is at most 64 after the last request', ({ne
|
|
|
316
316
|
- **Running it**: `rule.interpreter()` is persistent (searches branch from any state); `rule.compiled()` is what an app
|
|
317
317
|
ships: maps and the log updated in place, so a step costs the same however long the history. Both refuse params and
|
|
318
318
|
inputs that aren't naturals below 2^53 and any value past 2^53 − 1, where the proofs over the naturals stop describing
|
|
319
|
-
the run. The GPU searches refuse a space whose values could pass 2^32 − 1.
|
|
319
|
+
the run, and a request that fails the rule's `assume` (no proof covers the step it would take). The GPU searches refuse a space whose values could pass 2^32 − 1.
|
|
320
320
|
|
|
321
321
|
## Numbers
|
|
322
322
|
|
package/dist/core/dsl.js
CHANGED
|
@@ -2126,7 +2126,7 @@ export class Rule {
|
|
|
2126
2126
|
// with requests drawn by their kinds (or `draw`), must hold the same values as the compiled rule (decoded: integers and
|
|
2127
2127
|
// decimals as numbers) for every field the reference's state has, after every step of `trials` runs of `depth`.
|
|
2128
2128
|
compare(reference, opts = {}) {
|
|
2129
|
-
const s = this.spec, rand = opts.rand ?? seeded(5), bound = opts.bound ?? 100, gen = this.compiled();
|
|
2129
|
+
const s = this.spec, rand = opts.rand ?? seeded(5), bound = opts.bound ?? 100, gen = this.compiled(), run = interpret(this.machine);
|
|
2130
2130
|
let checked = 0;
|
|
2131
2131
|
const sample = (kinds) => Object.fromEntries(kinds.map(([n, k]) => [n, k.sample(rand, bound)]));
|
|
2132
2132
|
for (let trial = 0; trial < (opts.trials ?? 200); trial++) {
|
|
@@ -2134,8 +2134,11 @@ export class Rule {
|
|
|
2134
2134
|
let ours = gen.init(ps), theirs = reference.init(params);
|
|
2135
2135
|
const edges = trial === 0 ? opts.edges ?? [] : [];
|
|
2136
2136
|
for (let d = 0; d < (opts.depth ?? 10) + edges.length; d++) {
|
|
2137
|
-
const request = edges[d] ?? opts.draw?.(rand) ?? sample(s.inputs);
|
|
2138
|
-
|
|
2137
|
+
const request = edges[d] ?? opts.draw?.(rand) ?? sample(s.inputs), encoded = this.encodeInput(request, params);
|
|
2138
|
+
// A request the rule's assumption refuses is no step of the rule: the runtimes throw, so it isn't compared.
|
|
2139
|
+
if (!allowed(this.machine, run, ps, ours, encoded))
|
|
2140
|
+
continue;
|
|
2141
|
+
ours = gen.step(ps, ours, encoded);
|
|
2139
2142
|
theirs = reference.step(params, theirs, request);
|
|
2140
2143
|
const got = this.decode(ours);
|
|
2141
2144
|
for (const [field, want] of Object.entries(theirs)) {
|
package/dist/core/machine.js
CHANGED
|
@@ -272,6 +272,9 @@ export function carriedView(m, st, input, rename = map => map) {
|
|
|
272
272
|
}
|
|
273
273
|
// A request's numbers (its inputs), without the maps it carries.
|
|
274
274
|
export const numbersOf = (input) => Object.fromEntries(Object.entries(input).filter((e) => typeof e[1] === 'number'));
|
|
275
|
+
// A request the rule's assumption refuses: the proofs say nothing about the step it would take, so neither runtime takes
|
|
276
|
+
// it (a caller that may send one checks first, as gdp-wired/gdp's gate does).
|
|
277
|
+
const refused = (m) => `${m.name}: the request fails the rule's assumption, so no proof covers the step it would take`;
|
|
275
278
|
// The interpreter: persistent (a step leaves the state it was given as it was), so searches can branch from any state.
|
|
276
279
|
export function interpret(m) {
|
|
277
280
|
const { h, plain, carried } = runnable(m);
|
|
@@ -340,6 +343,8 @@ export function interpret(m) {
|
|
|
340
343
|
}
|
|
341
344
|
for (const [name, c] of Object.entries(plain.step.flags ?? {}))
|
|
342
345
|
env[`?${name}`] = evalC(c, env, st, plain) ? 1 : 0;
|
|
346
|
+
if (m.assume && !evalC(m.assume, env, st, plain))
|
|
347
|
+
throw new RangeError(refused(m));
|
|
343
348
|
const scalars = { ...st.scalars };
|
|
344
349
|
for (const [name, e] of Object.entries(plain.step.state ?? {}))
|
|
345
350
|
scalars[name] = evalE(e, env, st, plain);
|
|
@@ -371,6 +376,8 @@ const RUNTIME = [
|
|
|
371
376
|
['$m', `function $m(v, what) { if (!(v instanceof Map)) throw new RangeError(what + ' must be a Map of naturals'); for (const [key, x] of v) { $n(key, 'a key of ' + what); $n(x, 'a cell of ' + what); } return v; }`],
|
|
372
377
|
['$keys', `function $keys(maps, names) { const keys = new Set(); let past = 0; for (const c of Object.values(maps)) for (const key of c.keys()) past = Math.max(past, key + 1); for (const n of names) for (const key of maps[n].keys()) keys.add(key); return [...keys, past]; }`],
|
|
373
378
|
['$breaks', `function $breaks(maps, reads, cmps) { const p = new Set([0]); for (const [n, d, minus] of reads) { for (const s of maps[n].keys()) { if (minus) p.add(s + d); else if (s >= d) p.add(s - d); } if (minus) p.add(d); } for (const [v, d, minus] of cmps) { if (minus) { p.add(v + d); p.add(d); } else if (v >= d) p.add(v - d); } const keys = new Set(); for (const q of p) for (let i = -1; i <= 2; i++) if (q + i >= 0) keys.add(q + i); const sorted = [...keys].sort((a, b) => a - b); sorted.push(sorted[sorted.length - 1] + 1); return sorted; }`],
|
|
379
|
+
// A condition at every two keys that holds wherever x(k) differs from y(k1): only the pairs whose values match are read.
|
|
380
|
+
['$join', `function $join(ks, x, y, f) { const by = new Map(); for (const k1 of ks) { const v = y(k1), l = by.get(v); if (l) l.push(k1); else by.set(v, [k1]); } return ks.every(k => { const l = by.get(x(k)); return l === undefined || l.every(k1 => f(k, k1)); }); }`],
|
|
374
381
|
['$spread', `function $spread(keys, n) { const out = new Set(); for (const k of keys) for (let i = 0; i < n; i++) out.add(k + i); return [...out].sort((a, b) => a - b); }`],
|
|
375
382
|
['$dense', `function $dense(keys, f) { const top = keys[keys.length - 1]; if (top > 1000000) throw new RangeError('a write at every key reads its key in arithmetic past a million keys (' + top + ')'); const d = f(top), cells = new Map(); for (let k = 0; k < top; k++) { const v = f(k); if (v !== d) cells.set(k, v); } return {cells, d}; }`],
|
|
376
383
|
['$every', `function $every(keys, f) { const past = keys[keys.length - 1], d = f(past), cells = new Map(); for (let i = 0; i < keys.length - 1; i++) { const v = f(keys[i]); if (v !== d) cells.set(keys[i], v); } return {cells, d}; }`],
|
|
@@ -388,6 +395,22 @@ function runtimeFor(code) {
|
|
|
388
395
|
}
|
|
389
396
|
return RUNTIME.filter(([name]) => used.has(name)).map(([, src]) => `${src}\n`).join('');
|
|
390
397
|
}
|
|
398
|
+
// A condition over two keys, k and k1, that holds wherever a term of k alone differs from a term of k1 alone: an
|
|
399
|
+
// implication (a not-and, or an or with one) whose premises include x(k) = y(k1). The runtime then reads only the pairs
|
|
400
|
+
// whose values match ($join), the rest holding by the premise: rows plus matches, not rows squared.
|
|
401
|
+
function joinOf(c, k, k1) {
|
|
402
|
+
const reads = (t, key) => JSON.stringify(t).includes(`"op":"var","name":${JSON.stringify(key)}`);
|
|
403
|
+
const premises = c.op === 'not' && c.x.op === 'and' ? c.x.xs : c.op === 'or' ? c.xs.flatMap(x => (x.op === 'not' ? (x.x.op === 'and' ? x.x.xs : [x.x]) : [])) : [];
|
|
404
|
+
for (const p of premises) {
|
|
405
|
+
if (p.op !== '=')
|
|
406
|
+
continue;
|
|
407
|
+
if (reads(p.a, k) && !reads(p.a, k1) && reads(p.b, k1) && !reads(p.b, k))
|
|
408
|
+
return { x: p.a, y: p.b };
|
|
409
|
+
if (reads(p.b, k) && !reads(p.b, k1) && reads(p.a, k1) && !reads(p.a, k))
|
|
410
|
+
return { x: p.b, y: p.a };
|
|
411
|
+
}
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
391
414
|
export function compileJs(m) {
|
|
392
415
|
const { h, plain, queries, carried } = runnable(m);
|
|
393
416
|
const id = (name) => JSON.stringify(name);
|
|
@@ -427,6 +450,15 @@ export function compileJs(m) {
|
|
|
427
450
|
bound = { ...bound, ...Object.fromEntries(names.map((n, i) => [n, ks[i]])) };
|
|
428
451
|
const body = cx(c.c);
|
|
429
452
|
bound = outer;
|
|
453
|
+
if (names.length === 2) {
|
|
454
|
+
const j = joinOf(c.c, names[0], names[1]);
|
|
455
|
+
if (j) {
|
|
456
|
+
bound = { ...bound, [names[0]]: 'k', [names[1]]: 'k1' };
|
|
457
|
+
const x = ex(j.x), y = ex(j.y);
|
|
458
|
+
bound = outer;
|
|
459
|
+
return `$join(${keys}, k => ${x}, k1 => ${y}, (k, k1) => ${body})`;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
430
462
|
return names.length === 1 ? `${keys}.every(k => ${body})` : `(ks => ${ks.map(k => `ks.every(${k} => `).join('')}${body}${')'.repeat(ks.length)})(${keys})`;
|
|
431
463
|
}
|
|
432
464
|
}
|
|
@@ -465,7 +497,9 @@ export function compileJs(m) {
|
|
|
465
497
|
}
|
|
466
498
|
lines.push(`let ${queries.map((_, i) => `q${i} = 0`).join(', ')};`, `for (let i = head; i < records.length; i++) { const r = records[i]; ${queries.map((q, i) => `q${i} = $x(q${i} + ${ex(h.queries[q])});`).join(' ')} }`, ...queries.map((q, i) => `env[${id(q)}] = q${i};`));
|
|
467
499
|
}
|
|
468
|
-
lines.push(...flags.map((name, i) => `const flag${i} = ${cx(plain.step.flags[name])};`),
|
|
500
|
+
lines.push(...flags.map((name, i) => `const flag${i} = ${cx(plain.step.flags[name])};`),
|
|
501
|
+
// The rule's own assumption, as `allowed` (core/watch.ts) reads it (a history's machine assumes more: its relations).
|
|
502
|
+
...(m.assume ? [`if (!${cx(m.assume)}) throw new RangeError(${id(refused(m))});`] : []), `const scalars = {...s, ${Object.entries(plain.step.state ?? {}).map(([name, e]) => `${id(name)}: ${ex(e)}`).join(', ')}};`, ...writes.map(([, w], i) => ('each' in w ? `const write${i} = ${every(w)};` : `const key${i} = ${ex(w.key)}, value${i} = ${ex(w.value)};`)));
|
|
469
503
|
if (h)
|
|
470
504
|
lines.push(`const keep = ${cx(h.keep)};`);
|
|
471
505
|
lines.push(...writes.map(([name, w], i) => ('each' in w ? `state.maps[${id(name)}] = write${i}.cells; dft[${id(name)}] = write${i}.d;` : `state.maps[${id(name)}].set(key${i}, value${i});`)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdp-wired",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.7",
|
|
4
4
|
"description": "An automatic prover for stateful rules (budgets, caps, windows, workflows, policies): laws proven with no proof to write, checked again in Lean, counterexamples found on the GPU, the rule shipped as code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"verification",
|