tide-maxflow 0.1.0

Tide max flow algorithm — a push-pull-relabel variant with O(1) array-based data structures
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tide Max Flow: Benchmark Report</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<style>
  body { font-family: 'Segoe UI', system-ui, sans-serif; max-width: 1400px; margin: 0 auto; padding: 20px; background: #fafafa; color: #222; }
  h1 { font-size: 1.6em; border-bottom: 2px solid #333; padding-bottom: 8px; }
  h2 { font-size: 1.25em; margin-top: 2em; border-bottom: 1px solid #ccc; padding-bottom: 4px; }
  h3 { font-size: 1.05em; margin-top: 1.5em; color: #444; }
  .chart-container { background: #fff; border: 1px solid #ddd; border-radius: 6px; padding: 16px; margin: 16px 0; }
  .chart-row { display: flex; flex-wrap: wrap; gap: 16px; }
  .chart-half { flex: 1; min-width: 500px; }
  table { border-collapse: collapse; margin: 12px 0; font-size: 0.85em; width: 100%; }
  th, td { border: 1px solid #ccc; padding: 4px 8px; text-align: right; }
  th { background: #f0f0f0; font-weight: 600; }
  td:first-child, th:first-child { text-align: left; }
  .note { font-size: 0.85em; color: #666; margin: 8px 0; }
  .finding { background: #d4edda; border-left: 4px solid #28a745; padding: 10px 14px; margin: 16px 0; font-size: 0.95em; }
  .finding-neg { background: #f8d7da; border-left: 4px solid #dc3545; padding: 10px 14px; margin: 16px 0; font-size: 0.95em; }
  .finding-neutral { background: #fff3cd; border-left: 4px solid #ffc107; padding: 10px 14px; margin: 16px 0; font-size: 0.95em; }
  .win-tide { background: #d4edda; }
  .win-other { background: #f8d7da; }
  .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin: 16px 0; }
  .stat-card { background: #fff; border: 1px solid #ddd; border-radius: 6px; padding: 14px; text-align: center; }
  .stat-card .value { font-size: 2em; font-weight: 700; margin: 4px 0; }
  .stat-card .label { font-size: 0.85em; color: #666; }
  .stat-card.good .value { color: #28a745; }
  .stat-card.bad .value { color: #dc3545; }
  .stat-card.neutral .value { color: #ffc107; }
  .scrollable { max-height: 700px; overflow-y: auto; }
  .best-cell { background: #d4edda; font-weight: 700; }
  .conclusion { background: #e8eaf6; border-left: 4px solid #3f51b5; padding: 12px 16px; margin: 12px 0; font-size: 0.95em; }
  .conclusion ul { margin: 6px 0 0 0; padding-left: 20px; }
  .conclusion li { margin-bottom: 4px; }
</style>
</head>
<body>

<h1>Tide Max Flow: Benchmark Report</h1>
<p class="note">
  <strong>Solvers:</strong> Tide Standard (Rust), Tide Adaptive (Rust), Google OR-Tools (C++ push-relabel), HPF (C, Hochbaum Pseudoflow).<br>
  <strong>Graph pool:</strong> 97 DIMACS graphs across 11 families: layered, grid, chains, bipartite, random, washington, vision2d, vision3d, rfim2d, rfim3d, and Waterloo vision benchmarks. Graphs range from 10 edges to 210M edges.<br>
  <strong>Hardware:</strong> Apple Silicon, single-threaded, <code>--release</code> builds. All phases ran sequentially (no CPU contention).<br>
  <strong>Timing:</strong> Tide: internal <code>Instant</code> (min of 3 runs for adaptive, 1 run for standard). OR-Tools/HPF: <code>perf_counter</code> (min of 3 runs).<br>
  <strong>Correctness:</strong> All max flow values match exactly across all solvers on all 97 graphs.
</p>

<h2>1. Summary</h2>
<div class="summary-grid" id="summaryCards"></div>

<div class="finding">
  <strong>Tide</strong> is a push-relabel variant that operates in global <em>tides</em>: each tide performs
  a full BFS to compute exact distance labels, then a forward push sweep followed by a backward pull sweep.
  Structured graphs (layered, grid, chains) converge in 2&ndash;5 tides. Dense or vision-style graphs
  require 20&ndash;400+ tides.
</div>

<h2>2. Standard vs Adaptive Tide</h2>
<p class="note">
  <strong>Adaptive mode</strong> runs 5 standard BFS tides, then switches to local relabeling (skipping full BFS)
  until flow stalls. This helps on graphs that need many tides (bipartite, random, washington) but
  <em>hurts</em> on vision/RFIM graphs where local relabeling generates extra steps without reducing BFS cost.
</p>
<h3>2.1 By Family</h3>
<div class="chart-container"><canvas id="chartModesFamily"></canvas></div>
<div id="modesFamilyTable"></div>

<div class="finding">
  <strong>Adaptive is faster on combinatorial graphs:</strong> bipartite 2x, random 1.7x, washington 2.5x faster.
</div>
<div class="finding-neg">
  <strong>Standard is faster on vision/RFIM graphs:</strong> adaptive inflates step count 2&ndash;7x on
  vision and RFIM families, making it 1.4&ndash;1.9x slower. For these graphs, use standard mode.
</div>

<h3>2.2 All Graphs: Adaptive vs Standard</h3>
<div class="chart-container" style="height:700px"><canvas id="chartAdpStdAll"></canvas></div>

<h2>3. Tide vs OR-Tools (C++ Push-Relabel)</h2>
<p class="note">
  OR-Tools uses a highest-label push-relabel implementation in C++ with pybind11 bindings.
  We compare Tide Standard on all 97 graphs (including graphs &gt;1&thinsp;GB that exceed OR-Tools' file size limit).
</p>
<h3>3.1 By Family</h3>
<div class="chart-row">
  <div class="chart-half"><div class="chart-container"><canvas id="chartAdpOrtFamily"></canvas></div></div>
  <div class="chart-half"><div class="chart-container"><canvas id="chartAdpOrtBar"></canvas></div></div>
</div>
<div id="adpOrtFamilyTable"></div>

<h3>3.2 Head-to-Head: All Graphs</h3>
<div class="chart-container" style="height:700px"><canvas id="chartH2H"></canvas></div>

<h3>3.3 Scaling: Large Graphs</h3>
<div class="chart-container" style="height:500px"><canvas id="chartScaling"></canvas></div>

<h2>4. Tide vs HPF (Pseudoflow)</h2>
<p class="note">
  HPF (Hochbaum Pseudoflow, lowest-label FIFO) is the best-performing serial max-flow algorithm on
  vision graphs per Jensen et&nbsp;al.&nbsp;(2022&nbsp;TPAMI). It solves the minimum cut via pseudoflows
  without computing explicit flows. We compare Tide Standard against HPF.
</p>
<h3>4.1 By Family</h3>
<div class="chart-row">
  <div class="chart-half"><div class="chart-container"><canvas id="chartAdpHpfFamily"></canvas></div></div>
  <div class="chart-half"><div class="chart-container"><canvas id="chartAdpHpfBar"></canvas></div></div>
</div>
<div id="adpHpfFamilyTable"></div>

<h3>4.2 Head-to-Head: All Graphs</h3>
<div class="chart-container" style="height:700px"><canvas id="chartH2Hhpf"></canvas></div>

<h2>5. Tide Count as Performance Predictor</h2>
<div class="chart-row">
  <div class="chart-half"><div class="chart-container"><canvas id="chartSkipVsSpeedup"></canvas></div></div>
  <div class="chart-half"><div class="chart-container"><canvas id="chartTideCount"></canvas></div></div>
</div>
<div class="finding-neutral">
  <strong>Tide count predicts everything.</strong> Graphs that converge in &le;5 tides are Tide's sweet spot:
  10&ndash;100x faster than HPF, competitive with OR-Tools. Graphs requiring &gt;30 tides are where
  OR-Tools and HPF win. The crossover is sharp and predictable from graph structure.
</div>

<h2>6. Full Results Table</h2>
<div class="scrollable">
<table id="fullTable">
  <thead><tr>
    <th>Graph</th><th>Family</th><th>V</th><th>E</th><th>Flow</th>
    <th>Standard (ms)</th><th>Adaptive (ms)</th><th>OR-Tools (ms)</th><th>HPF (ms)</th>
    <th>Std/ORT</th><th>Std/HPF</th><th>Std Steps</th><th>Adp Steps</th>
  </tr></thead>
  <tbody></tbody>
</table>
</div>

<h2>7. Conclusions</h2>

<div class="conclusion">
  <strong>1. Tide dominates on structured graphs.</strong>
  <ul>
    <li>Layered graphs: <strong>Tide 1.1x faster</strong> than OR-Tools, <strong>100x faster</strong> than HPF (geo mean over 15 comparable graphs).</li>
    <li>Grid graphs: tied with OR-Tools, <strong>15x faster</strong> than HPF.</li>
    <li>Chains: tied with OR-Tools, <strong>14x faster</strong> than HPF.</li>
    <li>The advantage comes from Tide's global BFS sweep converging in 2&ndash;5 tides on these structures,
        yielding effectively O(V+E) total work.</li>
  </ul>
</div>

<div class="conclusion">
  <strong>2. OR-Tools and HPF win on vision and physics graphs.</strong>
  <ul>
    <li>Vision grids (t-links at every vertex): OR-Tools <strong>6&ndash;7x faster</strong>, HPF <strong>1.4&ndash;2x faster</strong>.</li>
    <li>RFIM lattices (PBC + random capacities): OR-Tools <strong>5&ndash;14x faster</strong>, HPF <strong>5&ndash;20x faster</strong>.</li>
    <li>Waterloo real vision benchmarks: OR-Tools <strong>7x faster</strong>, HPF <strong>7x faster</strong>.</li>
    <li>Root cause: these graphs require 20&ndash;400+ tides, each doing a full O(V+E) BFS.
        OR-Tools' local push-relabel with gap heuristics and HPF's pseudoflow method avoid this repeated global work.</li>
  </ul>
</div>

<div class="conclusion">
  <strong>3. Tide count is the universal predictor.</strong>
  <ul>
    <li>&le;5 tides: Tide is 10&ndash;100x faster than HPF, competitive with OR-Tools.</li>
    <li>5&ndash;30 tides: competitive range, depends on graph size and density.</li>
    <li>&gt;30 tides: OR-Tools and HPF win, gap grows with tide count.</li>
    <li>Tide count depends on graph structure, not size: a 210M-edge layered graph needs 3 tides,
        while a 50K-edge RFIM lattice needs 150+.</li>
  </ul>
</div>

<div class="conclusion">
  <strong>4. Adaptive mode is not universally better.</strong>
  <ul>
    <li>Adaptive helps on bipartite (2x), random (1.7x), washington (2.5x) by skipping BFS via local relabeling.</li>
    <li>Adaptive <em>hurts</em> on vision (1.4x slower) and RFIM (0.7&ndash;1.9x slower) because local relabeling
        inflates step count 2&ndash;7x without proportional BFS savings.</li>
    <li>Recommendation: use standard mode as default; switch to adaptive only for dense combinatorial graphs.</li>
  </ul>
</div>

<div class="conclusion">
  <strong>5. Tide is a strong general-purpose max-flow algorithm.</strong>
  <ul>
    <li>Pure Rust, single-threaded, no unsafe code, no external dependencies.</li>
    <li>Wins 63 of 87 graphs against HPF (the SOTA vision solver) by geometric mean 5.4x overall.</li>
    <li>Wins 26 of 87 graphs against OR-Tools, losing primarily on vision/RFIM families.</li>
    <li>Scales to 210M edges (layered_70000x1000: 6.0s, layered_10000x7000: 6.9s).</li>
    <li>Best suited for network flow, combinatorial optimization, and any application where
        graph structure yields low tide counts.</li>
  </ul>
</div>

<h2>8. Methodology</h2>
<ul>
  <li><strong>Tide Standard</strong>: <code>solve_dimacs --dir graph-pool/</code>. Rust <code>--release</code>, internal <code>Instant</code> timing, 1 run on all 97 graphs.</li>
  <li><strong>Tide Adaptive</strong>: <code>solve_dimacs --adaptive --dir graph-pool/</code>. Min of 3 runs on all 97 graphs. Warmup: 5 standard BFS tides before switching to local relabeling. Stall detection: bail after 2 consecutive tides without flow increase.</li>
  <li><strong>OR-Tools</strong>: Google OR-Tools 9.15, <code>SimpleMaxFlow</code> (C++ push-relabel via pybind11). <code>perf_counter</code> around <code>solve()</code>. Min of 3 runs. Graphs &gt;1&thinsp;GB excluded (Python memory limits).</li>
  <li><strong>HPF</strong>: Hochbaum Pseudoflow v3.23, lowest-label FIFO (<code>pseudo_fifo</code>). C, reads DIMACS from stdin. <code>perf_counter</code> around subprocess call (includes ~2&thinsp;ms startup overhead). Min of 3 runs. Graphs &gt;1&thinsp;GB excluded.</li>
  <li><strong>Graph pool</strong>: 97 DIMACS files across 11 families. Synthetic: layered DAGs (up to 210M edges), open-boundary grids, chains, complete bipartite, Erd&#337;s&ndash;R&eacute;nyi random, washington (stochastic). Vision: 2D/3D grids with bidirectional n-links + s/t t-links. Physics: 2D/3D RFIM lattices with periodic boundary conditions. Real: Waterloo vision benchmarks (liver, adhead, babyface, bone, LB07-bunny).</li>
  <li><strong>Hardware</strong>: Apple M-series Silicon, single-threaded. All solver phases ran sequentially with no CPU contention.</li>
  <li><strong>Correctness</strong>: All max flow values verified identical across all solvers on all graphs.</li>
</ul>

<script>
const DATA = [{"name": "LB07-bunny-med", "n": 6311088, "m": 38739041, "flow": 3860022, "std_us": 57433808, "std_steps": 109, "adp_us": 46875158, "adp_steps": 138, "adp_skip": 91, "hyb_us": 57433808, "hyb_steps": 109, "ort_us": 3762556, "hpf_us": 5851190}, {"name": "LB07-bunny-sml", "n": 805802, "m": 5040834, "flow": 961163, "std_us": 2680109, "std_steps": 48, "adp_us": 2766378, "adp_steps": 81, "adp_skip": 60, "hyb_us": 2680109, "hyb_steps": 48, "ort_us": 333093, "hpf_us": 646082}, {"name": "adhead.n6c10", "n": 12582914, "m": 75826316, "flow": 48373, "std_us": 229275933, "std_steps": 214, "adp_us": 230094362, "adp_steps": 1903, "adp_skip": 1819, "hyb_us": 229275933, "hyb_steps": 214, "ort_us": null, "hpf_us": null}, {"name": "babyface.n6c10", "n": 5062502, "m": 30386370, "flow": 19448, "std_us": 167190940, "std_steps": 304, "adp_us": 109772092, "adp_steps": 2096, "adp_skip": 2062, "hyb_us": 167190940, "hyb_steps": 304, "ort_us": 9913271, "hpf_us": 7480212}, {"name": "bipartite_1000x1000", "n": 2002, "m": 1002000, "flow": 124500, "std_us": 555073, "std_steps": 195, "adp_us": 137068, "adp_steps": 199, "adp_skip": 191, "hyb_us": 555073, "hyb_steps": 195, "ort_us": 21203, "hpf_us": 96690}, {"name": "bipartite_100x100", "n": 202, "m": 10200, "flow": 12450, "std_us": 756, "std_steps": 26, "adp_us": 371, "adp_steps": 28, "adp_skip": 20, "hyb_us": 756, "hyb_steps": 26, "ort_us": 215, "hpf_us": 3181}, {"name": "bipartite_10x10", "n": 22, "m": 120, "flow": 1215, "std_us": 11, "std_steps": 7, "adp_us": 11, "adp_steps": 9, "adp_skip": 1, "hyb_us": 11, "hyb_steps": 7, "ort_us": 14, "hpf_us": 2044}, {"name": "bipartite_2000x2000", "n": 4002, "m": 4004000, "flow": 249000, "std_us": 4891690, "std_steps": 384, "adp_us": 1064835, "adp_steps": 386, "adp_skip": 378, "hyb_us": 4891690, "hyb_steps": 384, "ort_us": 97433, "hpf_us": 374157}, {"name": "bipartite_200x200", "n": 402, "m": 40400, "flow": 24900, "std_us": 4904, "std_steps": 46, "adp_us": 1818, "adp_steps": 47, "adp_skip": 39, "hyb_us": 4904, "hyb_steps": 46, "ort_us": 774, "hpf_us": 5615}, {"name": "bipartite_20x20", "n": 42, "m": 440, "flow": 2480, "std_us": 26, "std_steps": 9, "adp_us": 24, "adp_steps": 11, "adp_skip": 3, "hyb_us": 26, "hyb_steps": 9, "ort_us": 23, "hpf_us": 2033}, {"name": "bipartite_500x500", "n": 1002, "m": 251000, "flow": 62250, "std_us": 66931, "std_steps": 101, "adp_us": 19676, "adp_steps": 104, "adp_skip": 96, "hyb_us": 66931, "hyb_steps": 101, "ort_us": 4471, "hpf_us": 24781}, {"name": "bipartite_50x50", "n": 102, "m": 2600, "flow": 6225, "std_us": 140, "std_steps": 15, "adp_us": 96, "adp_steps": 21, "adp_skip": 13, "hyb_us": 140, "hyb_steps": 15, "ort_us": 73, "hpf_us": 2221}, {"name": "bipartite_5x5", "n": 12, "m": 35, "flow": 570, "std_us": 5, "std_steps": 7, "adp_us": 6, "adp_steps": 9, "adp_skip": 1, "hyb_us": 5, "hyb_steps": 7, "ort_us": 10, "hpf_us": 1860}, {"name": "bone.n6c10", "n": 7798786, "m": 46920181, "flow": 5454, "std_us": 134408410, "std_steps": 122, "adp_us": 61416401, "adp_steps": 352, "adp_skip": 312, "hyb_us": 134408410, "hyb_steps": 122, "ort_us": null, "hpf_us": null}, {"name": "bone_subx.n6c10", "n": 3899394, "m": 23488978, "flow": 30590, "std_us": 82659568, "std_steps": 160, "adp_us": 21096746, "adp_steps": 473, "adp_skip": 454, "hyb_us": 82659568, "hyb_steps": 160, "ort_us": 6010852, "hpf_us": 3028366}, {"name": "bone_subxy.n6c10", "n": 1949698, "m": 11759514, "flow": 16405, "std_us": 28548018, "std_steps": 129, "adp_us": 8787789, "adp_steps": 341, "adp_skip": 316, "hyb_us": 28548018, "hyb_steps": 129, "ort_us": 2009793, "hpf_us": 1464141}, {"name": "bone_subxyz.n6c10", "n": 983042, "m": 5929493, "flow": 8014, "std_us": 8707774, "std_steps": 87, "adp_us": 3316993, "adp_steps": 246, "adp_skip": 225, "hyb_us": 8707774, "hyb_steps": 87, "ort_us": 639178, "hpf_us": 697820}, {"name": "bone_subxyz_subx.n6c10", "n": 491522, "m": 2972389, "flow": 5044, "std_us": 3308377, "std_steps": 68, "adp_us": 2789718, "adp_steps": 228, "adp_skip": 177, "hyb_us": 3308377, "hyb_steps": 68, "ort_us": 275871, "hpf_us": 338646}, {"name": "bone_subxyz_subxy.n6c10", "n": 245762, "m": 1489904, "flow": 2891, "std_us": 1025135, "std_steps": 51, "adp_us": 576684, "adp_steps": 173, "adp_skip": 153, "hyb_us": 1025135, "hyb_steps": 51, "ort_us": 95938, "hpf_us": 170210}, {"name": "chains_1000x1000", "n": 1000002, "m": 1001000, "flow": 20001, "std_us": 99100, "std_steps": 2, "adp_us": 97272, "adp_steps": 2, "adp_skip": 0, "hyb_us": 99100, "hyb_steps": 2, "ort_us": 98552, "hpf_us": 155017}, {"name": "chains_100x100", "n": 10002, "m": 10100, "flow": 6950, "std_us": 351, "std_steps": 2, "adp_us": 359, "adp_steps": 2, "adp_skip": 0, "hyb_us": 351, "hyb_steps": 2, "ort_us": 474, "hpf_us": 3522}, {"name": "chains_10x20", "n": 202, "m": 210, "flow": 245, "std_us": 17, "std_steps": 2, "adp_us": 17, "adp_steps": 2, "adp_skip": 0, "hyb_us": 17, "hyb_steps": 2, "ort_us": 16, "hpf_us": 1978}, {"name": "chains_2000x2000", "n": 4000002, "m": 4002000, "flow": 40000, "std_us": 420705, "std_steps": 2, "adp_us": 449702, "adp_steps": 2, "adp_skip": 0, "hyb_us": 420705, "hyb_steps": 2, "ort_us": 396773, "hpf_us": 648396}, {"name": "chains_20x10", "n": 202, "m": 220, "flow": 590, "std_us": 23, "std_steps": 2, "adp_us": 19, "adp_steps": 2, "adp_skip": 0, "hyb_us": 23, "hyb_steps": 2, "ort_us": 16, "hpf_us": 2336}, {"name": "chains_5000x1000", "n": 5000002, "m": 5005000, "flow": 100005, "std_us": 613407, "std_steps": 2, "adp_us": 609369, "adp_steps": 2, "adp_skip": 0, "hyb_us": 613407, "hyb_steps": 2, "ort_us": 634281, "hpf_us": 902126}, {"name": "chains_500x100", "n": 50002, "m": 50500, "flow": 111798, "std_us": 2322, "std_steps": 2, "adp_us": 2187, "adp_steps": 2, "adp_skip": 0, "hyb_us": 2322, "hyb_steps": 2, "ort_us": 2575, "hpf_us": 9025}, {"name": "chains_50x50", "n": 2502, "m": 2550, "flow": 2225, "std_us": 111, "std_steps": 2, "adp_us": 99, "adp_steps": 2, "adp_skip": 0, "hyb_us": 111, "hyb_steps": 2, "ort_us": 86, "hpf_us": 2400}, {"name": "chains_5x10", "n": 52, "m": 55, "flow": 110, "std_us": 6, "std_steps": 2, "adp_us": 4, "adp_steps": 2, "adp_skip": 0, "hyb_us": 6, "hyb_steps": 2, "ort_us": 11, "hpf_us": 1930}, {"name": "clrs", "n": 6, "m": 10, "flow": 23, "std_us": 3, "std_steps": 4, "adp_us": 3, "adp_steps": 4, "adp_skip": 1, "hyb_us": 3, "hyb_steps": 4, "ort_us": 10, "hpf_us": 1961}, {"name": "grid_1000x1000", "n": 1000002, "m": 2000000, "flow": 9970, "std_us": 92149, "std_steps": 7, "adp_us": 133753, "adp_steps": 17, "adp_skip": 6, "hyb_us": 92149, "hyb_steps": 7, "ort_us": 134432, "hpf_us": 549655}, {"name": "grid_100x100", "n": 10002, "m": 20000, "flow": 5450, "std_us": 402, "std_steps": 2, "adp_us": 403, "adp_steps": 2, "adp_skip": 0, "hyb_us": 402, "hyb_steps": 2, "ort_us": 468, "hpf_us": 4347}, {"name": "grid_10x10", "n": 102, "m": 200, "flow": 95, "std_us": 9, "std_steps": 2, "adp_us": 8, "adp_steps": 2, "adp_skip": 0, "hyb_us": 9, "hyb_steps": 2, "ort_us": 14, "hpf_us": 2071}, {"name": "grid_2000x2000", "n": 4000002, "m": 8000000, "flow": 19940, "std_us": 1051685, "std_steps": 16, "adp_us": 870854, "adp_steps": 32, "adp_skip": 9, "hyb_us": 1051685, "hyb_steps": 16, "ort_us": 486943, "hpf_us": 2584925}, {"name": "grid_200x200", "n": 40002, "m": 80000, "flow": 20900, "std_us": 1598, "std_steps": 2, "adp_us": 1583, "adp_steps": 2, "adp_skip": 0, "hyb_us": 1598, "hyb_steps": 2, "ort_us": 1793, "hpf_us": 11454}, {"name": "grid_20x20", "n": 402, "m": 800, "flow": 290, "std_us": 42, "std_steps": 2, "adp_us": 32, "adp_steps": 2, "adp_skip": 0, "hyb_us": 42, "hyb_steps": 2, "ort_us": 28, "hpf_us": 1951}, {"name": "grid_3000x3000", "n": 9000002, "m": 18000000, "flow": 29910, "std_us": 2667869, "std_steps": 19, "adp_us": 2520462, "adp_steps": 47, "adp_skip": 17, "hyb_us": 2667869, "hyb_steps": 19, "ort_us": 1056321, "hpf_us": 6087756}, {"name": "grid_4500x4500", "n": 20250002, "m": 40500000, "flow": 44865, "std_us": 3358947, "std_steps": 12, "adp_us": 3790345, "adp_steps": 26, "adp_skip": 8, "hyb_us": 3358947, "hyb_steps": 12, "ort_us": 2332404, "hpf_us": 13709016}, {"name": "grid_500x500", "n": 250002, "m": 500000, "flow": 127250, "std_us": 11238, "std_steps": 2, "adp_us": 10986, "adp_steps": 2, "adp_skip": 0, "hyb_us": 11238, "hyb_steps": 2, "ort_us": 12913, "hpf_us": 60428}, {"name": "grid_50x50", "n": 2502, "m": 5000, "flow": 1475, "std_us": 109, "std_steps": 2, "adp_us": 102, "adp_steps": 2, "adp_skip": 0, "hyb_us": 109, "hyb_steps": 2, "ort_us": 114, "hpf_us": 2536}, {"name": "grid_5x5", "n": 27, "m": 50, "flow": 35, "std_us": 9, "std_steps": 2, "adp_us": 5, "adp_steps": 2, "adp_skip": 0, "hyb_us": 9, "hyb_steps": 2, "ort_us": 11, "hpf_us": 1963}, {"name": "layered_10000x1000", "n": 10000002, "m": 29999000, "flow": 540379, "std_us": 741794, "std_steps": 3, "adp_us": 695119, "adp_steps": 3, "adp_skip": 0, "hyb_us": 741794, "hyb_steps": 3, "ort_us": 1028283, "hpf_us": 720223214}, {"name": "layered_10000x2000", "n": 20000002, "m": 59998000, "flow": 1080758, "std_us": 1477798, "std_steps": 3, "adp_us": 1459492, "adp_steps": 3, "adp_skip": 0, "hyb_us": 1477798, "hyb_steps": 3, "ort_us": null, "hpf_us": null}, {"name": "layered_10000x7000", "n": 70000002, "m": 209993000, "flow": 3782653, "std_us": 8284864, "std_steps": 3, "adp_us": 6889715, "adp_steps": 3, "adp_skip": 0, "hyb_us": 8284864, "hyb_steps": 3, "ort_us": null, "hpf_us": null}, {"name": "layered_1000x100", "n": 100002, "m": 299900, "flow": 9467, "std_us": 9742, "std_steps": 5, "adp_us": 9221, "adp_steps": 5, "adp_skip": 1, "hyb_us": 9742, "hyb_steps": 5, "ort_us": 6058, "hpf_us": 360907}, {"name": "layered_1000x1000", "n": 1000002, "m": 2999000, "flow": 540379, "std_us": 94291, "std_steps": 3, "adp_us": 80153, "adp_steps": 3, "adp_skip": 0, "hyb_us": 94291, "hyb_steps": 3, "ort_us": 183061, "hpf_us": 10421363}, {"name": "layered_100x100", "n": 10002, "m": 29900, "flow": 9467, "std_us": 836, "std_steps": 5, "adp_us": 824, "adp_steps": 5, "adp_skip": 1, "hyb_us": 836, "hyb_steps": 5, "ort_us": 919, "hpf_us": 12258}, {"name": "layered_10x10", "n": 102, "m": 290, "flow": 405, "std_us": 37, "std_steps": 8, "adp_us": 52, "adp_steps": 14, "adp_skip": 5, "hyb_us": 37, "hyb_steps": 8, "ort_us": 19, "hpf_us": 2059}, {"name": "layered_20000x2000", "n": 40000002, "m": 119998000, "flow": 1080758, "std_us": 3268888, "std_steps": 3, "adp_us": 2973267, "adp_steps": 3, "adp_skip": 0, "hyb_us": 3268888, "hyb_steps": 3, "ort_us": null, "hpf_us": null}, {"name": "layered_2000x1000", "n": 2000002, "m": 5999000, "flow": 540379, "std_us": 169616, "std_steps": 3, "adp_us": 177291, "adp_steps": 3, "adp_skip": 0, "hyb_us": 169616, "hyb_steps": 3, "ort_us": 274522, "hpf_us": 43279085}, {"name": "layered_2000x2000", "n": 4000002, "m": 11998000, "flow": 1080758, "std_us": 340184, "std_steps": 3, "adp_us": 324167, "adp_steps": 3, "adp_skip": 0, "hyb_us": 340184, "hyb_steps": 3, "ort_us": 606437, "hpf_us": 95590855}, {"name": "layered_200x50", "n": 10002, "m": 29950, "flow": 3392, "std_us": 877, "std_steps": 5, "adp_us": 831, "adp_steps": 5, "adp_skip": 1, "hyb_us": 877, "hyb_steps": 5, "ort_us": 645, "hpf_us": 15028}, {"name": "layered_20x10", "n": 202, "m": 590, "flow": 405, "std_us": 65, "std_steps": 8, "adp_us": 69, "adp_steps": 14, "adp_skip": 5, "hyb_us": 65, "hyb_steps": 8, "ort_us": 33, "hpf_us": 2134}, {"name": "layered_3000x3000", "n": 9000002, "m": 26997000, "flow": 1621137, "std_us": 700060, "std_steps": 3, "adp_us": 653961, "adp_steps": 3, "adp_skip": 0, "hyb_us": 700060, "hyb_steps": 3, "ort_us": 1283939, "hpf_us": 305943803}, {"name": "layered_50000x1000", "n": 50000002, "m": 149999000, "flow": 540379, "std_us": 3801220, "std_steps": 3, "adp_us": 3491309, "adp_steps": 3, "adp_skip": 0, "hyb_us": 3801220, "hyb_steps": 3, "ort_us": null, "hpf_us": null}, {"name": "layered_5000x1000", "n": 5000002, "m": 14999000, "flow": 540379, "std_us": 357631, "std_steps": 3, "adp_us": 334511, "adp_steps": 3, "adp_skip": 0, "hyb_us": 357631, "hyb_steps": 3, "ort_us": 559648, "hpf_us": 227646640}, {"name": "layered_500x100", "n": 50002, "m": 149900, "flow": 9467, "std_us": 4472, "std_steps": 5, "adp_us": 4333, "adp_steps": 5, "adp_skip": 1, "hyb_us": 4472, "hyb_steps": 5, "ort_us": 3266, "hpf_us": 126432}, {"name": "layered_500x500", "n": 250002, "m": 749500, "flow": 148067, "std_us": 26497, "std_steps": 5, "adp_us": 26133, "adp_steps": 5, "adp_skip": 1, "hyb_us": 26497, "hyb_steps": 5, "ort_us": 33604, "hpf_us": 1171782}, {"name": "layered_50x50", "n": 2502, "m": 7450, "flow": 3392, "std_us": 242, "std_steps": 5, "adp_us": 251, "adp_steps": 5, "adp_skip": 1, "hyb_us": 242, "hyb_steps": 5, "ort_us": 211, "hpf_us": 3638}, {"name": "layered_5x5", "n": 27, "m": 70, "flow": 183, "std_us": 9, "std_steps": 6, "adp_us": 10, "adp_steps": 7, "adp_skip": 0, "hyb_us": 9, "hyb_steps": 6, "ort_us": 13, "hpf_us": 2080}, {"name": "layered_70000x1000", "n": 70000002, "m": 209999000, "flow": 540379, "std_us": 6835902, "std_steps": 3, "adp_us": 5953748, "adp_steps": 3, "adp_skip": 0, "hyb_us": 6835902, "hyb_steps": 3, "ort_us": null, "hpf_us": null}, {"name": "liver.n6c10", "n": 4161602, "m": 25138821, "flow": 55104, "std_us": 101574377, "std_steps": 203, "adp_us": 50303358, "adp_steps": 582, "adp_skip": 553, "hyb_us": 101574377, "hyb_steps": 203, "ort_us": 9633592, "hpf_us": 4980497}, {"name": "random_10000_3pct", "n": 10002, "m": 3031508, "flow": 5015256, "std_us": 588024, "std_steps": 29, "adp_us": 217341, "adp_steps": 33, "adp_skip": 25, "hyb_us": 588024, "hyb_steps": 29, "ort_us": 75914, "hpf_us": 318769}, {"name": "random_10000_5pct", "n": 10002, "m": 5032346, "flow": 5003896, "std_us": 918806, "std_steps": 28, "adp_us": 306597, "adp_steps": 30, "adp_skip": 22, "hyb_us": 918806, "hyb_steps": 28, "ort_us": 298702, "hpf_us": 518446}, {"name": "random_1000_10pct", "n": 1002, "m": 102283, "flow": 486940, "std_us": 5443, "std_steps": 19, "adp_us": 3200, "adp_steps": 21, "adp_skip": 13, "hyb_us": 5443, "hyb_steps": 19, "ort_us": 1807, "hpf_us": 12391}, {"name": "random_1000_5pct", "n": 1002, "m": 52225, "flow": 486940, "std_us": 3255, "std_steps": 20, "adp_us": 1841, "adp_steps": 20, "adp_skip": 12, "hyb_us": 3255, "hyb_steps": 20, "ort_us": 1053, "hpf_us": 7312}, {"name": "random_2000_10pct", "n": 2002, "m": 404700, "flow": 994040, "std_us": 31043, "std_steps": 26, "adp_us": 14552, "adp_steps": 28, "adp_skip": 20, "hyb_us": 31043, "hyb_steps": 26, "ort_us": 7030, "hpf_us": 44164}, {"name": "random_2000_5pct", "n": 2002, "m": 204821, "flow": 994040, "std_us": 12708, "std_steps": 20, "adp_us": 6860, "adp_steps": 25, "adp_skip": 17, "hyb_us": 12708, "hyb_steps": 20, "ort_us": 3728, "hpf_us": 23919}, {"name": "random_200_10pct", "n": 202, "m": 4382, "flow": 9804, "std_us": 287, "std_steps": 14, "adp_us": 200, "adp_steps": 18, "adp_skip": 10, "hyb_us": 287, "hyb_steps": 14, "ort_us": 237, "hpf_us": 2326}, {"name": "random_20_10pct", "n": 22, "m": 80, "flow": 821, "std_us": 13, "std_steps": 7, "adp_us": 16, "adp_steps": 10, "adp_skip": 2, "hyb_us": 13, "hyb_steps": 7, "ort_us": 15, "hpf_us": 2156}, {"name": "random_20_30pct", "n": 22, "m": 161, "flow": 894, "std_us": 17, "std_steps": 7, "adp_us": 14, "adp_steps": 10, "adp_skip": 2, "hyb_us": 17, "hyb_steps": 7, "ort_us": 23, "hpf_us": 1958}, {"name": "random_5000_10pct", "n": 5002, "m": 2509966, "flow": 2483204, "std_us": 472315, "std_steps": 32, "adp_us": 146821, "adp_steps": 34, "adp_skip": 26, "hyb_us": 472315, "hyb_steps": 32, "ort_us": 57150, "hpf_us": 255841}, {"name": "random_5000_3pct", "n": 5002, "m": 762077, "flow": 2483204, "std_us": 74955, "std_steps": 24, "adp_us": 39433, "adp_steps": 26, "adp_skip": 18, "hyb_us": 74955, "hyb_steps": 24, "ort_us": 14585, "hpf_us": 81737}, {"name": "random_5000_5pct", "n": 5002, "m": 1262011, "flow": 2483204, "std_us": 145551, "std_steps": 26, "adp_us": 69445, "adp_steps": 28, "adp_skip": 20, "hyb_us": 145551, "hyb_steps": 26, "ort_us": 25036, "hpf_us": 131686}, {"name": "random_500_5pct", "n": 502, "m": 13453, "flow": 243954, "std_us": 753, "std_steps": 13, "adp_us": 548, "adp_steps": 15, "adp_skip": 7, "hyb_us": 753, "hyb_steps": 13, "ort_us": 301, "hpf_us": 3617}, {"name": "random_50_10pct", "n": 52, "m": 337, "flow": 2243, "std_us": 39, "std_steps": 12, "adp_us": 33, "adp_steps": 15, "adp_skip": 7, "hyb_us": 39, "hyb_steps": 12, "ort_us": 20, "hpf_us": 2099}, {"name": "random_50_30pct", "n": 52, "m": 822, "flow": 2243, "std_us": 53, "std_steps": 9, "adp_us": 48, "adp_steps": 11, "adp_skip": 3, "hyb_us": 53, "hyb_steps": 9, "ort_us": 26, "hpf_us": 2310}, {"name": "rfim2d_100", "n": 10002, "m": 50000, "flow": 3920956, "std_us": 30390, "std_steps": 126, "adp_us": 56570, "adp_steps": 339, "adp_skip": 301, "hyb_us": 30390, "hyb_steps": 126, "ort_us": 7632, "hpf_us": 10610}, {"name": "rfim2d_1000", "n": 1000002, "m": 5000000, "flow": 389738714, "std_us": 13984715, "std_steps": 177, "adp_us": 25884707, "adp_steps": 901, "adp_skip": 876, "hyb_us": 13984715, "hyb_steps": 177, "ort_us": 1548663, "hpf_us": 1680146}, {"name": "rfim2d_2000", "n": 4000002, "m": 20000000, "flow": 1559383895, "std_us": 84660469, "std_steps": 237, "adp_us": 156303587, "adp_steps": 1254, "adp_skip": 1187, "hyb_us": 84660469, "hyb_steps": 237, "ort_us": 8626121, "hpf_us": 7910375}, {"name": "rfim2d_500", "n": 250002, "m": 1250000, "flow": 97415858, "std_us": 2322680, "std_steps": 152, "adp_us": 4351575, "adp_steps": 699, "adp_skip": 668, "hyb_us": 2322680, "hyb_steps": 152, "ort_us": 261766, "hpf_us": 322368}, {"name": "rfim3d_100", "n": 1000002, "m": 7000000, "flow": 909871729, "std_us": 26222808, "std_steps": 188, "adp_us": 18440550, "adp_steps": 419, "adp_skip": 388, "hyb_us": 26222808, "hyb_steps": 188, "ort_us": 3186122, "hpf_us": 2883171}, {"name": "rfim3d_128", "n": 2097154, "m": 14680064, "flow": 1907704749, "std_us": 69437903, "std_steps": 222, "adp_us": 51317424, "adp_steps": 553, "adp_skip": 513, "hyb_us": 69437903, "hyb_steps": 222, "ort_us": 8429166, "hpf_us": 7304102}, {"name": "rfim3d_32", "n": 32770, "m": 229376, "flow": 29475709, "std_us": 95208, "std_steps": 65, "adp_us": 113996, "adp_steps": 119, "adp_skip": 102, "hyb_us": 95208, "hyb_steps": 65, "ort_us": 38658, "hpf_us": 38742}, {"name": "rfim3d_64", "n": 262146, "m": 1835008, "flow": 238735951, "std_us": 2841738, "std_steps": 100, "adp_us": 1995679, "adp_steps": 173, "adp_skip": 153, "hyb_us": 2841738, "hyb_steps": 100, "ort_us": 475127, "hpf_us": 494482}, {"name": "vision2d_1000x1000", "n": 1000002, "m": 5996000, "flow": 189467415, "std_us": 1006394, "std_steps": 18, "adp_us": 1441530, "adp_steps": 29, "adp_skip": 16, "hyb_us": 1006394, "hyb_steps": 18, "ort_us": 227122, "hpf_us": 920355}, {"name": "vision2d_100x100", "n": 10002, "m": 59600, "flow": 1873058, "std_us": 5390, "std_steps": 13, "adp_us": 9105, "adp_steps": 23, "adp_skip": 11, "hyb_us": 5390, "hyb_steps": 13, "ort_us": 1643, "hpf_us": 9318}, {"name": "vision2d_2000x2000", "n": 4000002, "m": 23992000, "flow": 757998713, "std_us": 4745193, "std_steps": 22, "adp_us": 6735975, "adp_steps": 35, "adp_skip": 21, "hyb_us": 4745193, "hyb_steps": 22, "ort_us": 927934, "hpf_us": 3951873}, {"name": "vision2d_5000x5000", "n": 25000002, "m": 149980000, "flow": 4737545868, "std_us": 35317770, "std_steps": 25, "adp_us": 50280748, "adp_steps": 42, "adp_skip": 26, "hyb_us": 35317770, "hyb_steps": 25, "ort_us": null, "hpf_us": null}, {"name": "vision2d_500x500", "n": 250002, "m": 1498000, "flow": 47313344, "std_us": 246038, "std_steps": 17, "adp_us": 317291, "adp_steps": 25, "adp_skip": 13, "hyb_us": 246038, "hyb_steps": 17, "ort_us": 49090, "hpf_us": 208447}, {"name": "vision3d_100x100x100", "n": 1000002, "m": 7940000, "flow": 200324292, "std_us": 1937743, "std_steps": 24, "adp_us": 2410126, "adp_steps": 36, "adp_skip": 23, "hyb_us": 1937743, "hyb_steps": 24, "ort_us": 357438, "hpf_us": 1287593}, {"name": "vision3d_200x200x200", "n": 8000002, "m": 63760000, "flow": 1604473564, "std_us": 18582416, "std_steps": 27, "adp_us": 24521150, "adp_steps": 45, "adp_skip": 31, "hyb_us": 18582416, "hyb_steps": 27, "ort_us": null, "hpf_us": null}, {"name": "vision3d_300x300x300", "n": 27000002, "m": 215460000, "flow": 5417422349, "std_us": 63676989, "std_steps": 26, "adp_us": 96629528, "adp_steps": 50, "adp_skip": 34, "hyb_us": 63676989, "hyb_steps": 26, "ort_us": null, "hpf_us": null}, {"name": "vision3d_50x50x50", "n": 125002, "m": 985000, "flow": 24995415, "std_us": 174537, "std_steps": 18, "adp_us": 281462, "adp_steps": 34, "adp_skip": 21, "hyb_us": 174537, "hyb_steps": 18, "ort_us": 39665, "hpf_us": 133720}, {"name": "washington_10x100", "n": 1002, "m": 90200, "flow": 49642, "std_us": 4877, "std_steps": 23, "adp_us": 2143, "adp_steps": 25, "adp_skip": 17, "hyb_us": 4877, "hyb_steps": 23, "ort_us": 1081, "hpf_us": 11243}, {"name": "washington_10x500", "n": 5002, "m": 2251000, "flow": 237226, "std_us": 375682, "std_steps": 68, "adp_us": 69967, "adp_steps": 77, "adp_skip": 67, "hyb_us": 375682, "hyb_steps": 68, "ort_us": 46414, "hpf_us": 234421}, {"name": "washington_20x200", "n": 4002, "m": 760400, "flow": 98620, "std_us": 48478, "std_steps": 28, "adp_us": 19421, "adp_steps": 30, "adp_skip": 22, "hyb_us": 48478, "hyb_steps": 28, "ort_us": 8744, "hpf_us": 81629}, {"name": "washington_5x200", "n": 1002, "m": 160400, "flow": 98620, "std_us": 5327, "std_steps": 12, "adp_us": 4389, "adp_steps": 14, "adp_skip": 6, "hyb_us": 5327, "hyb_steps": 12, "ort_us": 1903, "hpf_us": 18156}];

function fam(n){
  for(const p of['vision3d','vision2d','rfim3d','rfim2d','bipartite','chains','grid','layered','random','washington'])if(n.startsWith(p))return p;
  for(const p of['liver','adhead','babyface','bone','abdomen','LB07','BVZ','KZ2'])if(n.startsWith(p))return'waterloo';
  return'other';
}
const FC={layered:'#2196F3',grid:'#4CAF50',chains:'#8BC34A',bipartite:'#F44336',random:'#FF9800',washington:'#9C27B0',vision2d:'#00BCD4',vision3d:'#009688',rfim2d:'#795548',rfim3d:'#607D8B',waterloo:'#E91E63'};
function gm(a){return a.length?Math.exp(a.reduce((s,v)=>s+Math.log(v),0)/a.length):NaN}

const D = DATA.map(d => ({...d, f: fam(d.name), rS: d.adp_us/d.std_us, rO: d.ort_us ? d.std_us/d.ort_us : null, rP: d.hpf_us ? d.std_us/d.hpf_us : null}));
const NT = D.filter(d => d.m >= 500 && (d.std_us >= 100 || d.adp_us >= 100));
const FAMS = ['layered','grid','chains','vision2d','vision3d','rfim2d','rfim3d','waterloo','washington','random','bipartite'];

// 1. Summary cards
{
  const c=document.getElementById('summaryCards');
  const gS=gm(NT.map(d=>d.rS)), wS=NT.filter(d=>d.rS<1).length;
  const vO=NT.filter(d=>d.rO!==null), gO=gm(vO.map(d=>d.rO)), wO=vO.filter(d=>d.rO<1).length;
  const vP=NT.filter(d=>d.rP!==null), gP=gm(vP.map(d=>d.rP)), wP=vP.filter(d=>d.rP<1).length;
  const cards=[
    {l:'Tide vs HPF',v:gP.toFixed(2)+'x',s:'Tide wins '+wP+'/'+vP.length+' graphs',cl:gP<1?'good':'bad'},
    {l:'Tide vs OR-Tools',v:gO.toFixed(2)+'x',s:'Tide wins '+wO+'/'+vO.length+' graphs',cl:gO<1?'good':'bad'},
    {l:'Adaptive vs Standard',v:gS.toFixed(2)+'x',s:(gS<1?'Adaptive ':'Standard ')+((gS<1?1/gS:gS).toFixed(1))+'x faster, wins '+wS+'/'+NT.length,cl:gS<1?'good':'neutral'},
    {l:'Largest Graph',v:'210M E',s:'layered_70000x1000: 6.0s (3 tides)',cl:'good'},
    {l:'All Flows Correct',v:D.length+'/'+D.length,s:'All solvers match on all graphs',cl:'good'},
  ];
  c.innerHTML=cards.map(x=>`<div class="stat-card ${x.cl}"><div class="label">${x.l}</div><div class="value">${x.v}</div><div class="label">${x.s}</div></div>`).join('');
}

// 2.1 Modes comparison by family
{
  const fd=FAMS.map(f=>{
    const v=NT.filter(d=>d.f===f);
    return{f,n:v.length,
      gA:gm(v.map(d=>d.rS)),wA:v.filter(d=>d.rS<1).length};
  });
  new Chart(document.getElementById('chartModesFamily'),{type:'bar',data:{labels:fd.map(d=>d.f[0].toUpperCase()+d.f.slice(1)),datasets:[
    {label:'Adaptive / Standard',data:fd.map(d=>d.gA),backgroundColor:fd.map(d=>d.gA<1?'rgba(76,175,80,0.7)':'rgba(244,67,54,0.7)'),borderColor:fd.map(d=>d.gA<1?'#4CAF50':'#F44336'),borderWidth:1}
  ]},options:{plugins:{title:{display:true,text:'Adaptive / Standard Geo Mean by Family (<1 = Adaptive faster)'},legend:{display:false}},scales:{y:{min:0,max:2}}}});

  let h='<table><thead><tr><th>Family</th><th>Graphs</th><th>Adp/Std</th><th>Adaptive Wins</th><th>Verdict</th></tr></thead><tbody>';
  for(const d of fd){
    const e=d.gA<1?'Adaptive '+(1/d.gA).toFixed(1)+'x faster':'Standard '+(d.gA).toFixed(1)+'x faster';
    h+=`<tr class="${d.gA<1?'win-tide':'win-other'}"><td>${d.f[0].toUpperCase()+d.f.slice(1)}</td><td>${d.n}</td><td>${d.gA.toFixed(2)}x</td><td>${d.wA}/${d.n}</td><td>${e}</td></tr>`;
  }
  const allGA=gm(NT.map(d=>d.rS)),allWA=NT.filter(d=>d.rS<1).length;
  h+=`<tr style="font-weight:700;background:#f0f0f0"><td>Overall</td><td>${NT.length}</td><td>${allGA.toFixed(2)}x</td><td>${allWA}/${NT.length}</td><td>${allGA<1?'Adaptive '+(1/allGA).toFixed(1)+'x faster':'Standard '+(allGA).toFixed(1)+'x faster'}</td></tr>`;
  h+='</tbody></table>';
  document.getElementById('modesFamilyTable').innerHTML=h;
}

// 2.2 All graphs bar chart
{
  const s=[...NT].sort((a,b)=>a.rS-b.rS);
  new Chart(document.getElementById('chartAdpStdAll'),{type:'bar',data:{labels:s.map(d=>d.name.replace(/_/g,' ')),datasets:[
    {label:'Adaptive / Standard',data:s.map(d=>d.rS),backgroundColor:s.map(d=>d.rS<0.9?'#4CAF50':d.rS>1.1?'#F44336':'#FFC107')}
  ]},options:{indexAxis:'y',plugins:{title:{display:true,text:'Adaptive / Standard Ratio (<1 = Adaptive faster)'},legend:{display:false}},scales:{x:{min:0,max:2},y:{ticks:{font:{size:9}}}}}});
}

// 3.1 Adaptive vs ORT by family
{
  const fd=FAMS.map(f=>{const v=NT.filter(d=>d.f===f&&d.rO!==null);return{f,n:v.length,w:v.filter(d=>d.rO<1).length,g:gm(v.map(d=>d.rO))}}).filter(d=>d.n>0);
  new Chart(document.getElementById('chartAdpOrtFamily'),{type:'bar',data:{labels:fd.map(d=>d.f[0].toUpperCase()+d.f.slice(1)),datasets:[
    {label:'Geo Mean (Std/ORT)',data:fd.map(d=>d.g),backgroundColor:fd.map(d=>d.g<1?'#4CAF50':'#F44336')}
  ]},options:{plugins:{title:{display:true,text:'Tide Standard / OR-Tools Geo Mean by Family'},legend:{display:false}},scales:{y:{type:'logarithmic',min:0.3,max:20}}}});

  new Chart(document.getElementById('chartAdpOrtBar'),{type:'bar',data:{labels:fd.map(d=>d.f[0].toUpperCase()+d.f.slice(1)),datasets:[
    {label:'Tide wins',data:fd.map(d=>d.w),backgroundColor:'#4CAF50'},
    {label:'OR-Tools wins',data:fd.map(d=>d.n-d.w),backgroundColor:'#E91E63'}
  ]},options:{plugins:{title:{display:true,text:'Win Count: Tide Standard vs OR-Tools'}},scales:{x:{stacked:true},y:{stacked:true}}}});

  let h='<table><thead><tr><th>Family</th><th>Graphs</th><th>Tide Wins</th><th>Geo Mean</th><th>Verdict</th></tr></thead><tbody>';
  for(const d of fd){
    const e=d.g<1?'Tide '+(1/d.g).toFixed(1)+'x faster':'OR-Tools '+d.g.toFixed(1)+'x faster';
    h+=`<tr class="${d.g<1?'win-tide':'win-other'}"><td>${d.f[0].toUpperCase()+d.f.slice(1)}</td><td>${d.n}</td><td>${d.w}/${d.n}</td><td>${d.g.toFixed(2)}x</td><td>${e}</td></tr>`;
  }
  const allV=NT.filter(d=>d.rO!==null),allG=gm(allV.map(d=>d.rO)),allW=allV.filter(d=>d.rO<1).length;
  h+=`<tr style="font-weight:700;background:#f0f0f0"><td>Overall</td><td>${allV.length}</td><td>${allW}/${allV.length}</td><td>${allG.toFixed(2)}x</td><td>${allG<1?'Tide '+(1/allG).toFixed(1)+'x faster':'OR-Tools '+allG.toFixed(1)+'x faster'}</td></tr>`;
  h+='</tbody></table>';
  document.getElementById('adpOrtFamilyTable').innerHTML=h;
}

// 3.2 H2H bar chart
{
  const s=[...NT].filter(d=>d.rO!==null).sort((a,b)=>a.rO-b.rO);
  new Chart(document.getElementById('chartH2H'),{type:'bar',data:{labels:s.map(d=>d.name.replace(/_/g,' ')),datasets:[
    {label:'Tide Standard / OR-Tools',data:s.map(d=>d.rO),backgroundColor:s.map(d=>d.rO<1?'#4CAF50':'#F44336')}
  ]},options:{indexAxis:'y',plugins:{title:{display:true,text:'Tide Standard / OR-Tools (log scale, <1 = Tide faster)'},legend:{display:false}},scales:{x:{type:'logarithmic',min:0.1,max:20},y:{ticks:{font:{size:9}}}}}});
}

// 3.3 Scaling chart
{
  const large=D.filter(d=>d.m>=500000).sort((a,b)=>a.m-b.m);
  new Chart(document.getElementById('chartScaling'),{type:'bar',data:{labels:large.map(d=>d.name.replace(/_/g,' ')),datasets:[
    {label:'Tide Standard',data:large.map(d=>d.std_us/1e6),backgroundColor:'rgba(33,150,243,0.7)',borderColor:'#2196F3',borderWidth:1},
    {label:'OR-Tools',data:large.map(d=>d.ort_us?d.ort_us/1e6:null),backgroundColor:'rgba(233,30,99,0.6)',borderColor:'#E91E63',borderWidth:1},
    {label:'HPF',data:large.map(d=>d.hpf_us?d.hpf_us/1e6:null),backgroundColor:'rgba(121,85,72,0.7)',borderColor:'#795548',borderWidth:1},
  ]},options:{plugins:{title:{display:true,text:'Solve Time on Large Graphs (E >= 500K, seconds, log scale)'}},scales:{y:{type:'logarithmic',min:0.001,title:{display:true,text:'Time (s)'}}}}});
}

// 4. HPF comparison
{
  const fd=FAMS.map(f=>{const v=NT.filter(d=>d.f===f&&d.rP!==null);return{f,n:v.length,w:v.filter(d=>d.rP<1).length,g:gm(v.map(d=>d.rP))}}).filter(d=>d.n>0);
  new Chart(document.getElementById('chartAdpHpfFamily'),{type:'bar',data:{labels:fd.map(d=>d.f[0].toUpperCase()+d.f.slice(1)),datasets:[
    {label:'Geo Mean (Std/HPF)',data:fd.map(d=>d.g),backgroundColor:fd.map(d=>d.g<1?'#4CAF50':'#F44336')}
  ]},options:{plugins:{title:{display:true,text:'Tide Standard / HPF Geo Mean by Family'},legend:{display:false}},scales:{y:{type:'logarithmic',min:0.01,max:100}}}});

  new Chart(document.getElementById('chartAdpHpfBar'),{type:'bar',data:{labels:fd.map(d=>d.f[0].toUpperCase()+d.f.slice(1)),datasets:[
    {label:'Tide wins',data:fd.map(d=>d.w),backgroundColor:'#4CAF50'},
    {label:'HPF wins',data:fd.map(d=>d.n-d.w),backgroundColor:'#795548'}
  ]},options:{plugins:{title:{display:true,text:'Win Count: Tide Standard vs HPF'}},scales:{x:{stacked:true},y:{stacked:true}}}});

  let h='<table><thead><tr><th>Family</th><th>Graphs</th><th>Tide Wins</th><th>Geo Mean</th><th>Verdict</th></tr></thead><tbody>';
  for(const d of fd){
    const e=d.g<1?'Tide '+(1/d.g).toFixed(1)+'x faster':'HPF '+d.g.toFixed(1)+'x faster';
    h+=`<tr class="${d.g<1?'win-tide':'win-other'}"><td>${d.f[0].toUpperCase()+d.f.slice(1)}</td><td>${d.n}</td><td>${d.w}/${d.n}</td><td>${d.g.toFixed(2)}x</td><td>${e}</td></tr>`;
  }
  const allV=NT.filter(d=>d.rP!==null),allG=gm(allV.map(d=>d.rP)),allW=allV.filter(d=>d.rP<1).length;
  h+=`<tr style="font-weight:700;background:#f0f0f0"><td>Overall</td><td>${allV.length}</td><td>${allW}/${allV.length}</td><td>${allG.toFixed(2)}x</td><td>${allG<1?'Tide '+(1/allG).toFixed(1)+'x faster':'HPF '+allG.toFixed(1)+'x faster'}</td></tr>`;
  h+='</tbody></table>';
  document.getElementById('adpHpfFamilyTable').innerHTML=h;
}

// 4.2 H2H HPF bar chart
{
  const s=[...NT].filter(d=>d.rP!==null).sort((a,b)=>a.rP-b.rP);
  new Chart(document.getElementById('chartH2Hhpf'),{type:'bar',data:{labels:s.map(d=>d.name.replace(/_/g,' ')),datasets:[
    {label:'Tide Standard / HPF',data:s.map(d=>d.rP),backgroundColor:s.map(d=>d.rP<1?'#4CAF50':'#F44336')}
  ]},options:{indexAxis:'y',plugins:{title:{display:true,text:'Tide Standard / HPF (log scale, <1 = Tide faster)'},legend:{display:false}},scales:{x:{type:'logarithmic',min:0.001,max:100},y:{ticks:{font:{size:9}}}}}});
}

// 5. BFS skips
{
  new Chart(document.getElementById('chartSkipVsSpeedup'),{type:'scatter',data:{datasets:FAMS.map(f=>({
    label:f[0].toUpperCase()+f.slice(1),
    data:NT.filter(d=>d.f===f).map(d=>({x:d.adp_skip,y:d.rS})),
    backgroundColor:FC[f],pointRadius:6
  }))},options:{plugins:{title:{display:true,text:'BFS Skips vs Adaptive/Standard Ratio'}},scales:{x:{title:{display:true,text:'BFS Skips'}},y:{title:{display:true,text:'Adaptive/Standard (<1 = faster)'},min:0,max:2}}}});

  new Chart(document.getElementById('chartTideCount'),{type:'scatter',data:{datasets:FAMS.map(f=>({
    label:f[0].toUpperCase()+f.slice(1),
    data:NT.filter(d=>d.f===f&&d.rO!==null).map(d=>({x:d.std_steps,y:d.rO})),
    backgroundColor:FC[f],pointRadius:6
  }))},options:{plugins:{title:{display:true,text:'Standard Tide Steps vs Std/OR-Tools Ratio'}},scales:{x:{type:'logarithmic',title:{display:true,text:'Standard Steps (tides)'},min:1},y:{type:'logarithmic',title:{display:true,text:'Std/ORT (<1 = Tide faster)'},min:0.1,max:20}}}});
}

// 6. Full table
{
  const tb=document.querySelector('#fullTable tbody');
  const s=[...D].sort((a,b)=>(a.f<b.f?-1:a.f>b.f?1:a.m-b.m));
  for(const d of s){
    const sm=d.std_us/1000,am=d.adp_us/1000,om=d.ort_us?d.ort_us/1000:null,pm=d.hpf_us?d.hpf_us/1000:null;
    const fmt=v=>v===null?'-':v<0.01?v.toFixed(4):v<1?v.toFixed(3):v<100?v.toFixed(1):v.toFixed(0);
    const rO=d.rO?d.rO.toFixed(2):'-',rP=d.rP?d.rP.toFixed(2):'-';
    const best=Math.min(d.std_us,d.ort_us||Infinity,d.hpf_us||Infinity);
    const tr=document.createElement('tr');
    tr.innerHTML=`<td>${d.name}</td><td>${d.f}</td><td>${d.n.toLocaleString()}</td><td>${d.m.toLocaleString()}</td><td>${d.flow.toLocaleString()}</td>`
      +`<td class="${d.std_us===best?'best-cell':''}">${fmt(sm)}</td>`
      +`<td>${fmt(am)}</td>`
      +`<td class="${d.ort_us&&d.ort_us===best?'best-cell':''}">${om!==null?fmt(om):'-'}</td>`
      +`<td class="${d.hpf_us&&d.hpf_us===best?'best-cell':''}">${pm!==null?fmt(pm):'-'}</td>`
      +`<td>${rO}</td><td>${rP}</td>`
      +`<td>${d.std_steps}</td><td>${d.adp_steps}</td>`;
    tb.appendChild(tr);
  }
}
</script>
</body>
</html>