api: /interdict grows a ladder mode with a server-computed diverges flag (closes #38) - #61
Merged
Conversation
The whole Edmonds-Karp run ships as one array on one request -- every augmentation, complete enough to draw, each a full snapshot rather than a delta, so a display steps backwards by decrementing an index and no server-side cursor is needed. The server prepends a step 0 holding the graph before the first push: the uniform render rule is residuals from step k-1 and path annotation from step k, and iteration 1 otherwise has no "before" to draw against. Synthesising one client-side would put residual-graph construction in the browser. The data layer stops filtering and the CLI starts. Both filters existed only to tidy terminal output and both damaged a drawn trace: stripping the synthetic terminals made a route appear to begin mid-graph, and dropping the unbounded terms de-aligned lane_residuals from path, so a min(...) term could not be anchored to the lane it came from. Both now ship complete -- terminals under the reserved __source__/__sink__ ids, unbounded residuals as null -- and `len(lane_residuals) == len(path) - 1` holds on every augmentation. Each step also carries explicit per-edge flows covering every arc, saturated ones included, which the residual lists cannot say: they drop an arc the moment it saturates, and those are the lanes Day 2 most wants drawn. The CLI's text output is unchanged to the byte, pinned by a golden transcript that was written and passing against the old code before any of this moved.
… the trace The contract test asserted `len(lane_residuals) == max(len(path) - 1, 0)`, which is the one shape of that assertion that would also pass on a real augmentation de-aligned all the way down to no terms -- the exact failure it exists to catch. Step 0 is now asserted as what it is (no path, no terms) and every augmentation carries the equality strictly. The `iterations + 1` check compared the array's length to its own contents, which holds just as well on a trace that lost every augmentation. The textbook theater's three iterations are pinned as a count from outside it. README gains the `/maxflow?trace=true` payload alongside the `/scenario` and `/naive` ones, and the endpoint table stops omitting the new parameter.
api: /maxflow?trace=true ships the whole trace statelessly (closes #36)
…lag (closes #38) Day 4 walks up k with both interdiction methods side by side. `interdiction_ladder` runs the exhaustive optimum and the greedy heuristic at every rung -- both requested explicitly, because `method="auto"` silently returns exhaustive on any classroom-sized network and never computes greedy at all -- and stops at the first k where *both* tracks reach zero. On `greedy_trap` that is k=3, one rung past exhaustive's zero, which is where the lesson lands: greedy spends three lanes to buy what the optimum bought with two. Termination and the identical-rung collapse live here rather than in a render pass because both are arithmetic over solver results, and arithmetic over solver results is testable. The boot cost also drops from 2 x K x datasets calls to one per dataset. `diverges` compares the removed *sets*, not the residuals: at k=3 on the trap both tracks read zero and the sets differ by a lane, and a residual comparison would report that rung as agreement. `BudgetInterdiction` gains `searched`/`skipped` alongside `subsets_considered`. The latter is the size of the space -- the denominator both tracks share -- not a claim about either method's work; reporting it as greedy's own count would assert work greedy declined to do, and the declining is the point. The single-k and min-cut response shapes are untouched, and pinned as such.
The `searched`/`skipped` pair reports work done; it does not rank the two methods. The exhaustive search stops the moment it severs the network, so on a small instance it can finish in fewer evaluations than greedy -- on `textbook_maxflow` at k=2 the optimum searches 9 and greedy searches 13. Two tests asserted the opposite as if it were a law, on the one dataset where it happens to hold. Both now pin greedy's count against the *space*, which is the honest claim, and the inversion is pinned outright so it cannot later read as a regression. Also: the `ladder` parameter's OpenAPI text said it ignored `method` while the handler 400s on it; `_removed_lanes` is declared once instead of spelled twice; /interdict's wire helpers move to the helper cluster with a note on why this endpoint hand-builds its dicts; and the module's tolerance is one named `_EPS` rather than seven literals and a second constant beside them. `k_through == k` is now asserted rather than `>=`, which is the collapse guard reporting itself idle instead of a tautology.
# Conflicts: # src/clopt/api.py
origin/classdemos carried the original #57 trace work, which main later superseded via its own implementation. Both expose the same CLI surface (a single --trace flag); main's is the version CI runs and ships, so this merge joins the histories without reverting main's refactor of throughput.py, maxflow.py and cli.py.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #38.
/interdictgrows a ladder mode: rungs k=1..K, each carrying both methods' removed lane set, residual throughput and counts, plus a server-computeddivergesflag. The existing single-k shape is unchanged, so the CLI is untouched.The point of putting this on the server is that termination and the identical-rung collapse become facts a test can pin, rather than logic living in the render pass. The boot fetch also drops from 2 × K × datasets calls to one per dataset.
Acceptance criteria
Each is pinned by a named test in
tests/test_interdiction_ladder.py:test_rungs_start_at_one_and_step_by_one,test_every_rung_carries_both_tracksautonever produces a ladder —autosilently picks exhaustive on any classroom-sized dataset, so it would never compute the greedy result at alldivergesflag —test_the_trap_diverges_from_k_two_on,test_the_agreeing_dataset_states_its_agreementtest_ladder_stops_where_both_tracks_reach_zero,test_termination_waits_for_the_worse_method, derived from data rather than hard-codedtest_the_collapse_test_is_the_removed_set_not_the_residual; the test is the removed set, not the residual, so two blockades reaching the same number by different lanes stay two picturestest_greedy_reports_the_work_greedy_did_not_the_space,test_searched_and_skipped_account_for_the_whole_spacetest_k_one_never_diverges; greedy's first iteration is exactly the exhaustive search over singletons, so the earliest possible divergence is k=2Notes for review
Branch state
This branch also carries two merges that bring it level with
main(the frontend shell and day-4 trap) and join theorigin/classdemoshistory. The second used-s ours:origin/classdemosheld the original #57 trace implementation thatmainlater superseded, and a plain merge would have revertedmain's refactor ofthroughput.py,maxflow.pyandcli.py. Both expose the same CLI surface (a single--traceflag), so nothing user-facing is lost. The net diff againstmainis the five files above.Test plan
python -m pytest -q— 289 passed locally094a1aa