fix(08): WR-01 — oracle-sweep optimality compare is symmetric (|Δ| both directions)
- sweepOracle: Math.abs(deltaS) > ORACLE_EPSILON_S replaces the one-sided 'seconds > optimal + eps' check — a result FASTER than the Dijkstra oracle (the traversal-law-break signature, e.g. a oneway relaxed) is now a violation, stamped ORACLE-MISMATCH, never a pass - worstDeltaPct tracks MAGNITUDE not sign — a -40% law break moves it - pass gate: suboptimal === 0 && oracleMismatch === 0 && classBreaks === 0 - header gate description + exit codes updated: the check is |Δ| in both directions; GATE FAIL names both violation classes; GATE PASS reads 0/N |Δ| violations - row-level signed Δ printout unchanged (sign/direction reporting kept)
This commit is contained in:
@@ -33,9 +33,15 @@
|
|||||||
* if the raw draw runs out first the run fails honestly. Same binary +
|
* if the raw draw runs out first the run fails honestly. Same binary +
|
||||||
* seed ⇒ the same compared set, always.
|
* seed ⇒ the same compared set, always.
|
||||||
*
|
*
|
||||||
* Gate: 0 suboptimal pairs. Equal-cost ties pass (seconds only, never node
|
* Gate: 0 |Δ| pairs — the compare is SYMMETRIC (08-review WR-01): the
|
||||||
* paths — the D-03 tie law); null-vs-null (both sides unreachable) passes;
|
* oracle is the ground truth in BOTH directions. A result slower than the
|
||||||
* a verdict-class break (one side routes, the other does not) is a
|
* oracle stamps SUBOPTIMAL; a result FASTER than the oracle stamps
|
||||||
|
* ORACLE-MISMATCH — the traversal-law-break signature (e.g. edgeFlags
|
||||||
|
* bit0 relaxed would cut a oneway and come in BELOW the legal optimum),
|
||||||
|
* which the original one-sided `seconds > optimal + ε` check wrongly
|
||||||
|
* stamped pass. Equal-cost ties pass (seconds only, never node paths —
|
||||||
|
* the D-03 tie law); null-vs-null (both sides unreachable) passes; a
|
||||||
|
* verdict-class break (one side routes, the other does not) is a
|
||||||
* violation.
|
* violation.
|
||||||
*
|
*
|
||||||
* Honest-skip law: the real binary is gitignored — when it is absent this
|
* Honest-skip law: the real binary is gitignored — when it is absent this
|
||||||
@@ -48,8 +54,9 @@
|
|||||||
* Usage:
|
* Usage:
|
||||||
* node routing/scripts/measure-astar-oracle-sweep.mjs
|
* node routing/scripts/measure-astar-oracle-sweep.mjs
|
||||||
*
|
*
|
||||||
* Exit codes: 0 sweep passed (or the honest skip); 1 any suboptimal /
|
* Exit codes: 0 sweep passed (or the honest skip); 1 any |Δ| violation
|
||||||
* class-break violation, or an undecodable binary.
|
* (SUBOPTIMAL or ORACLE-MISMATCH) or class-break violation, or an
|
||||||
|
* undecodable binary.
|
||||||
*/
|
*/
|
||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
@@ -188,12 +195,13 @@ function dijkstraSeconds(graph, origin, goal) {
|
|||||||
* @param graph decoded RoadGraph (the current binary)
|
* @param graph decoded RoadGraph (the current binary)
|
||||||
* @param pairs generateCrossMetroPairs output (fresh, seeded)
|
* @param pairs generateCrossMetroPairs output (fresh, seeded)
|
||||||
* @param opts { snap, astar, oracle } — the live modules + the oracle
|
* @param opts { snap, astar, oracle } — the live modules + the oracle
|
||||||
* @returns {{ rows: Array<{ pairId, seconds: number|null, oracleSeconds: number|null, deltaPct: number|null, verdict: "pass"|"SUBOPTIMAL"|"unreachable"|"unsnappable"|"class-break" }>, suboptimal: number, classBreaks: number, routed: number, unreachable: number, unsnappable: number, worstDeltaPct: number, pass: boolean }}
|
* @returns {{ rows: Array<{ pairId, seconds: number|null, oracleSeconds: number|null, deltaPct: number|null, verdict: "pass"|"SUBOPTIMAL"|"ORACLE-MISMATCH"|"unreachable"|"unsnappable"|"class-break" }>, suboptimal: number, oracleMismatch: number, classBreaks: number, routed: number, unreachable: number, unsnappable: number, worstDeltaPct: number, pass: boolean }}
|
||||||
*/
|
*/
|
||||||
export function sweepOracle(graph, pairs, opts) {
|
export function sweepOracle(graph, pairs, opts) {
|
||||||
const { snap, astar, oracle } = opts;
|
const { snap, astar, oracle } = opts;
|
||||||
const rows = [];
|
const rows = [];
|
||||||
let suboptimal = 0;
|
let suboptimal = 0;
|
||||||
|
let oracleMismatch = 0;
|
||||||
let classBreaks = 0;
|
let classBreaks = 0;
|
||||||
let routed = 0;
|
let routed = 0;
|
||||||
let unreachable = 0;
|
let unreachable = 0;
|
||||||
@@ -227,21 +235,30 @@ export function sweepOracle(graph, pairs, opts) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
routed += 1;
|
routed += 1;
|
||||||
const deltaPct = ((result.seconds - optimal) / optimal) * 100;
|
// SYMMETRIC compare (08-review WR-01): |Δ| beyond the epsilon is a
|
||||||
const suboptimalHere = result.seconds > optimal + ORACLE_EPSILON_S;
|
// violation in BOTH directions. A FASTER-than-oracle result is not a
|
||||||
if (suboptimalHere) suboptimal += 1;
|
// pass — it is the traversal-law-break signature (e.g. a oneway
|
||||||
if (deltaPct > worstDeltaPct) worstDeltaPct = deltaPct;
|
// relaxed), labeled ORACLE-MISMATCH for a distinct escalation.
|
||||||
|
const deltaS = result.seconds - optimal;
|
||||||
|
const deltaPct = (deltaS / optimal) * 100;
|
||||||
|
const violates = Math.abs(deltaS) > ORACLE_EPSILON_S;
|
||||||
|
if (violates) {
|
||||||
|
if (deltaS > 0) suboptimal += 1;
|
||||||
|
else oracleMismatch += 1;
|
||||||
|
}
|
||||||
|
// Track MAGNITUDE, not sign: a -40% law break must move worstDeltaPct.
|
||||||
|
if (Math.abs(deltaPct) > Math.abs(worstDeltaPct)) worstDeltaPct = deltaPct;
|
||||||
rows.push({
|
rows.push({
|
||||||
pairId: pair.pairId,
|
pairId: pair.pairId,
|
||||||
seconds: result.seconds,
|
seconds: result.seconds,
|
||||||
oracleSeconds: optimal,
|
oracleSeconds: optimal,
|
||||||
deltaPct,
|
deltaPct,
|
||||||
verdict: suboptimalHere ? "SUBOPTIMAL" : "pass",
|
verdict: !violates ? "pass" : deltaS > 0 ? "SUBOPTIMAL" : "ORACLE-MISMATCH",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const pass = suboptimal === 0 && classBreaks === 0;
|
const pass = suboptimal === 0 && oracleMismatch === 0 && classBreaks === 0;
|
||||||
return { rows, suboptimal, classBreaks, routed, unreachable, unsnappable, worstDeltaPct, pass };
|
return { rows, suboptimal, oracleMismatch, classBreaks, routed, unreachable, unsnappable, worstDeltaPct, pass };
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ————— CLI reporter ————— */
|
/* ————— CLI reporter ————— */
|
||||||
@@ -306,20 +323,24 @@ async function main() {
|
|||||||
const delta = row.deltaPct === null ? "Δ —" : `Δ ${row.deltaPct >= 0 ? "+" : ""}${row.deltaPct.toFixed(3)}%`;
|
const delta = row.deltaPct === null ? "Δ —" : `Δ ${row.deltaPct >= 0 ? "+" : ""}${row.deltaPct.toFixed(3)}%`;
|
||||||
console.log(` ${row.pairId}: astar ${mine} vs oracle ${truth} (${delta}) ${row.verdict}`);
|
console.log(` ${row.pairId}: astar ${mine} vs oracle ${truth} (${delta}) ${row.verdict}`);
|
||||||
}
|
}
|
||||||
|
const worst = results.worstDeltaPct;
|
||||||
console.log(
|
console.log(
|
||||||
`summary: ${results.routed} routed (worst Δ +${results.worstDeltaPct.toFixed(3)}%), ` +
|
`summary: ${results.routed} routed (worst |Δ| ${worst >= 0 ? "+" : ""}${worst.toFixed(3)}%), ` +
|
||||||
`${results.unreachable} unreachable, ${results.unsnappable} unsnappable, ` +
|
`${results.unreachable} unreachable, ${results.unsnappable} unsnappable, ` +
|
||||||
`${results.suboptimal} SUBOPTIMAL, ${results.classBreaks} class-breaks`,
|
`${results.suboptimal} SUBOPTIMAL, ${results.oracleMismatch} ORACLE-MISMATCH, ` +
|
||||||
|
`${results.classBreaks} class-breaks`,
|
||||||
);
|
);
|
||||||
if (!results.pass) {
|
if (!results.pass) {
|
||||||
console.error(
|
console.error(
|
||||||
`GATE FAIL: ${results.suboptimal} suboptimal + ${results.classBreaks} class-break pair(s) across ${results.rows.length} fresh pairs — ` +
|
`GATE FAIL: ${results.suboptimal} SUBOPTIMAL + ${results.oracleMismatch} ORACLE-MISMATCH + ` +
|
||||||
`routeAstar seconds must equal the Dijkstra oracle (gate: 0 suboptimal). Escalate: this is RC-1's signature.`,
|
`${results.classBreaks} class-break pair(s) across ${results.rows.length} fresh pairs — ` +
|
||||||
|
`routeAstar seconds must equal the Dijkstra oracle in BOTH directions (gate: 0 |Δ| violations). ` +
|
||||||
|
`An ORACLE-MISMATCH (faster than the oracle) is a traversal-law break; a SUBOPTIMAL is RC-1's signature. Escalate.`,
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log(
|
console.log(
|
||||||
`GATE PASS: 0/${results.rows.length} suboptimal — shipped routeAstar equals the brute-force oracle on every fresh pair`,
|
`GATE PASS: 0/${results.rows.length} |Δ| violations — shipped routeAstar equals the brute-force oracle in both directions on every fresh pair`,
|
||||||
);
|
);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user