Skip to content

[mcmf] Pack edges inline into a CSR adjacency - #75

Open
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1785265659-mcmf-csr
Open

[mcmf] Pack edges inline into a CSR adjacency#75
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1785265659-mcmf-csr

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Data-oriented rewrite of the adjacency layout in mcmf.hpp (all three of MCMF_SSPA, MCMF_Dinic, Dinic), motivated by benchmarks showing the old std::vector<std::vector<int>> adj + shared edges array pays two dependent random loads per scanned edge:

old: adj[u] -> heap-allocated vector of edge ids -> edges[e].{dest,cap,cost}
new: csr[adj_start[u] .. adj_start[u+1]) with edge_t{dest, rev, cap[, cost]} stored inline

The staged edge list (input, with per-edge net flow) is the source of truth: each solve pack()s it into the scratch CSR (O(N+M), free relative to the solve) and unpack()s flows back afterwards, so add_edge works freely between solves. add_edge returns an edge id and flow_t flow(int id) reads back net flow (for add_bi_edge, in [-rev_cap, cap]); the new verify solutions use it to reconstruct matchings. Since forward/reverse edges are no longer at e/e^1, each CSR record carries a rev index.

Benchmarks (g++-15 -O2, seeds fixed, 3 trials each; Dinic<int>/Dinic<int64_t>):

  • bipartite matching (L=R=200k, deg 6): ~1.3x vs old layout
  • random sparse (N=200k, M=1M): ~1.3x
  • 700x700 grid: ~1.5x
  • MCMF_SSPA assignment (5000+5000, deg 30): ~1.3x

Also benchmarked but not adopted:

  • CSR of edge-ids only (keeping the shared edge array): ~1.0x — inlining the payload is the actual win, not flattening the vector-of-vectors.
  • vector<vector<edge_t>> with inline payload: ties CSR on unstructured graphs (0.9–1.05x) but loses 1.1–1.25x on grid-like graphs, where the single contiguous allocation keeps consecutive nodes' edges on shared cache lines.
  • index/SoA layouts for lct.hpp: 0.86–0.99x — splay touches every field of a node, so splitting fields only adds cache lines.

Adds Library Checker verification: verify/bipartitematching.test.cpp (Dinic + flow() readback) and verify/assignment.test.cpp (MCMF_SSPA with costs shifted by 1e9 to satisfy the nonnegative reduced-cost precondition); both AC locally in the normal and sanitizer environments.

Link to Devin session: https://app.devin.ai/sessions/13b190fd3b54418395ae04b4e3aaa6e1
Requested by: @ecnerwala


Open in Devin Review (Beta)

Replaces vector<vector<int>> adj + shared AoS edge array with a flat CSR
layout whose edge records (dest, rev, cap[, cost]) are stored inline per
node, so BFS/DFS/Dijkstra scan contiguous memory with one fewer
indirection. Benchmarks: ~1.3x on random/bipartite graphs, ~1.5x on grid
graphs for Dinic; ~1.3x for MCMF_SSPA assignment.

add_edge now returns an edge id and flow(id) reads back per-edge flow.
Adds Library Checker verification for bipartitematching and assignment.

Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
@ecnerwala ecnerwala self-assigned this Jul 28, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@beta-devin-ai-integration beta-devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review (Beta)

Comment thread src/mcmf.hpp

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Min-cost flow solver crashes when its layered-augmentation routine is used on its own

The min-cost Dinic solver's augmentation routine never prepares its internal edge storage (missing build() call at src/mcmf.hpp:266-267), unlike the plain max-flow solver which does, so calling it directly reads memory that was never set up and the program crashes or misbehaves.
Impact: Users who invoke the min-cost solver's per-phase routines directly (rather than the combined solve entry points) hit undefined behavior instead of getting a correct answer.

Missing lazy build in MCMF_Dinic::dinic while Dinic::dinic has it

After this PR the adjacency is packed lazily by build(). Dinic::dinic calls build() at src/mcmf.hpp:397, and MCMF_Dinic::all_flows/max_flow call it at src/mcmf.hpp:294 and src/mcmf.hpp:306, but MCMF_Dinic::dinic (src/mcmf.hpp:266) does not. If it (or dijkstra) is invoked before any solve, adj_start is empty, so adj_start[cur] / adj_start[cur+1] in the BFS loop (src/mcmf.hpp:276) index an empty vector and it.assign(adj_start.begin(), adj_start.end()-1) (src/mcmf.hpp:286) forms an out-of-range iterator. Previously these methods worked standalone since the adjacency was materialized in add_edge. The same gap applies to MCMF_SSPA::dijkstra/path and to flow(), which is const and therefore cannot build either.

(Refers to lines 266-267)

Open in Devin Review (Beta)

Was this helpful? React with 👍 or 👎 to provide feedback.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

GCC Code Coverage Report

📂 Overall coverage

Metric Coverage
Lines 🟡 7704/9366 (82.3%)
Functions 🟢 1016/1120 (90.7%)
Branches 🟡 6115/7805 (78.3%)

…lves

The edge list with per-edge flow is now the source of truth: each solve
packs it into the scratch CSR and writes flows back afterwards, so
add_edge works freely between solves and flow(id) is always valid.

Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant