graph LR A["1 ยท Profile\ncProfile / perf / cargo"] --> B["2 ยท Read the hot path\ncumulative time"] B --> C["3 ยท Fix the asymptotics\nnot the constant"] C --> D["4 ยท Re-measure\nsame workload"] D --> E{"Regression?"} E -->|"no"| F["5 ยท Verify\nfull test suite"] E -->|"yes"| C style A fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style B fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style C fill:#fff9c4,stroke:#f57f17,stroke-width:3px style D fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style E fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style F fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
graph TD P["๐ Slow hot path"] P --> A["โ Recompute what never changes\nrebuild BCC / ear decomposition\nevery call"] P --> B["โก Re-scan the whole set\nreverse-delete re-checks\nall edges per candidate"] P --> C["โข Look up by name, not index\nnode_indices().find()\nper BFS step"] style P fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style A fill:#fff9c4,stroke:#f57f17,stroke-width:3px style B fill:#fff9c4,stroke:#f57f17,stroke-width:3px style C fill:#fff9c4,stroke:#f57f17,stroke-width:3px
two_opt wall time ยท Python (log-free, max = 29.0 s) n=50 432 ms n=100 3 588 ms n=200 29 014 ms n=50 13 ms n=100 52 ms n=200 294 ms ยท 99ร Red = before ยท Green = after โ the green bars are the same workload, 99ร smaller.
graph TD subgraph P["Before ยท remove"] A["i-1"] -->|"w1"| B1["i"] C["j-1"] -->|"w2"| D["j"] end subgraph N["After ยท add"] A2["i-1"] -->|"w1'"| D2["j-1"] B2["i"] -->|"w2'"| C2["j"] end style A fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style B1 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style C fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style D fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style A2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style D2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style B2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style C2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
graph LR M["Compute delta\n(4 lookups)"] --> T{"delta < 0.0 ?"} T -->|"true (noise)"| R["Reverse segment\n(no real change)"] R --> M T -->|"delta < -1e-9"| S["Accept real move โ "] style M fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style T fill:#fff9c4,stroke:#f57f17,stroke-width:3px style R fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style S fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
graph TD subgraph BEFORE["Before ยท every violation"] B1["biconnected_components()"] --> B2["chain_decomposition()"] --> B3["BFS from every source"] --> B4["first cycle"] end subgraph AFTER["After ยท cached + one pass"] A1["_collect_cyclable_edges()\ncomputed once ๐๏ธ"] --> A2["one BFS per component\n(global visited)"] --> A3["stop at first cycle โ "] end style B1 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style B2 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style B3 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style B4 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style A1 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style A2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style A3 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
Before (red) vs After (green) ยท Python min_cycle_cover 150/400 1 859 ms 21 ms ยท 88ร min_vertex_cover 500/2500 380 ms 4.7 ms ยท 81ร two_opt n=200 29 014 ms 294 ms ยท 99ร Cycle cover went from super-linear to near-linear in graph size.
graph LR PY["netlistx.py\ncover ยท tsp ยท hadlock"] NC["netlistx-cpp\npd_cover ยท reverse_delete"] XN["xnetwork-cpp\ngeneric_bfs_cycle ยท tsp ยท hadlock"] PY -.->|"hypergraph cover"| NC PY -.->|"graph cover ยท TSP ยท MAX-CUT"| XN NC -->|"depends on"| XN style PY fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style NC fill:#ffe0b2,stroke:#e65100,stroke-width:3px style XN fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
graph LR S["for source in nodes\n(no visited set)"] --> BFS["BFS whole component"] BFS --> CP["copy info dict\nper back edge ๐"] CP --> S NEW["visited set\none BFS / component"] --> EXIT["stop at first cycle"] style S fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style BFS fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style CP fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style NEW fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style EXIT fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
graph TD subgraph OLD["O(kยทE) ยท rescan everything"] O1["remove v"] --> O2["is_valid()\nscan all nets/edges"] --> O3["re-add if broken"] end subgraph NEW["O(deg) ยท local test"] N1["remove v"] --> N2["all(neighbour โ cover)\nfor gr[v] only"] --> N3["re-add if broken"] end style O1 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style O2 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style O3 fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style N1 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style N2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style N3 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
min_cycle_cover on grid graphs ยท C++ (max = 20.3 s) 8ร8 before 890 ms 10ร10 before 5 661 ms 12ร12 before 20 327 ms 8ร8 after <1 ms 10ร10 after 1 ms 12ร12 after 2 ms ยท ~10 000ร The old 40ร40 grid effectively hung; the new code does it in ~325 ms.
graph LR B["12ร12 grid\nold code"] --> T["20.3 s โฑ๏ธ"] T --> G["40ร40 grid\nold code"] --> H["never finished ๐ฅถ"] H --> R["restore stash\n(mandatory!) ๐"] style B fill:#fff9c4,stroke:#f57f17,stroke-width:3px style T fill:#ffe0b2,stroke:#e65100,stroke-width:3px style G fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style H fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style R fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
graph LR F["find_edge()\nO(deg) per hop ๐"] --> BAD["O(n^4) sweep ๐ข"] M["adjacency matrix\nO(1) lookup ๐๏ธ"] --> GOOD["O(n^2) sweep โก"] style F fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style BAD fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style M fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style GOOD fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
graph LR M["MST"] --> O["k odd-degree\nvertices"] O --> D{"k โค 18 ?"} D -->|"yes"| E["exact DP\n2^k states โ "] D -->|"no"| X["1 << k\nastronomical โ"] X --> AB["allocation fails\nabort() ๐ฅ"] style M fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style O fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style D fill:#fff9c4,stroke:#f57f17,stroke-width:3px style E fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style X fill:#f8bbd0,stroke:#c2185b,stroke-width:3px style AB fill:#f8bbd0,stroke:#c2185b,stroke-width:3px
graph LR K{"k odd vertices"} -->|"k โค 18"| EX["exact DP\n3/2 guarantee โ "] K -->|"k > 18"| GR["greedy matching\nheuristic โ ๏ธ"] style K fill:#fff9c4,stroke:#f57f17,stroke-width:3px style EX fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style GR fill:#ffe0b2,stroke:#e65100,stroke-width:3px
two_opt ยท Rust release (max = 979 ms) n=50 before 69.09 ms n=100 before 978.6 ms n=50 after 0.072 ms ยท ~955ร n=100 after 0.246 ms ยท ~3 986ร n=200 after 1.2 ms โก
graph TD Q["Where does the time go?"] Q -->|"interpreted"| PY["Python\nrecompute + re-scan ๐"] Q -->|"compiled"| CC["C++ / Rust\nsame asymptotics โ๏ธ๐ฆ"] PY --> P1["cache ยท localize ยท index โ "] CC --> C1["early-exit scan\none BFS / component โ "] CC --> C2["index, not name ๐งฑ"] CC --> C3["bound the allocation\n(greedy fallback) ๐ฉน"] style Q fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style PY fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style CC fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style P1 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style C1 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style C2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px style C3 fill:#ffe0b2,stroke:#e65100,stroke-width:3px