Skip to content

Add flow cover cuts at root#1178

Open
hlinsen wants to merge 33 commits into
NVIDIA:mainfrom
hlinsen:flow-cover-cuts
Open

Add flow cover cuts at root#1178
hlinsen wants to merge 33 commits into
NVIDIA:mainfrom
hlinsen:flow-cover-cuts

Conversation

@hlinsen
Copy link
Copy Markdown
Contributor

@hlinsen hlinsen commented May 5, 2026

Closes: #1065

  • Adds c-MIR flow-cover cut generation for 0-1 single-node-flow relaxations.
  • Converts eligible MIP rows into single-node-flow form using binary variables and continuous flow variables.
  • On the Wolter benchmark, gap closed is now 8.45% shifted geomean. Wolter reports 10.92% in Table 5.1 and 10.02% in B.49.

@hlinsen hlinsen added this to the 26.06 milestone May 5, 2026
@hlinsen hlinsen requested a review from a team as a code owner May 5, 2026 07:07
@hlinsen hlinsen added the feature request New feature or request label May 5, 2026
@hlinsen hlinsen requested review from chris-maes and nguidotti May 5, 2026 07:07
@hlinsen hlinsen added the non-breaking Introduces a non-breaking change label May 5, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds flow-cover cut support to the MIP solver (new cut type, settings, generation logic, integration into cut pipeline, and tests) and extends the LP benchmark script with recursive model discovery and a cut-mode flag to control cut-related CLI options.

Changes

Flow Cover Cut Implementation & Integration

Layer / File(s) Summary
Type & Constant Definitions
cpp/src/cuts/cuts.hpp, cpp/include/cuopt/linear_programming/constants.h
Adds FLOW_COVER cut type and label; defines CUOPT_MIP_FLOW_COVER_CUTS ("mip_flow_cover_cuts").
Settings & Configuration
cpp/include/cuopt/linear_programming/mip/solver_settings.hpp, cpp/src/dual_simplex/simplex_solver_settings.hpp, cpp/src/math_optimization/solver_settings.cu, cpp/src/mip_heuristics/solver.cu
Introduces flow_cover_cuts setting (type i_t, default -1) across solver settings, registers it for parameter parsing, and maps it into B&B initialization.
Includes & Constraint Discovery
cpp/src/cuts/cuts.cpp
Adds headers and prepares knapsack generation constructor for populating flow_cover_constraints_ by scanning rows for valid flow-cover patterns.
Core Algorithm Implementation
cpp/src/cuts/cuts.hpp, cpp/src/cuts/cuts.cpp
Implements generate_flow_cover_cut(...) (SNF relaxation, cover selection, c‑MIR/SGFCI choice), extends knapsack solver with STRICT_RATIO_PREFIX, and adds generate_flow_cover_cuts(...) integration that inserts FLOW_COVER cuts.
variable_bounds Preprocessing
cpp/src/cuts/cuts.cpp
Introduces variable_bound_edge_t, switches sigma-slack formulas to sign-based checks, refines edge scans to skip self/slack columns, and stores only non-continuous edges.
Tests / Validation
cpp/tests/mip/cuts_test.cu
Adds a GTest flow_cover_generates_valid_single_node_flow_cut with a hand-built single-node flow MPS, test scaffolding, fractional point and extreme-point checks, and assertions that generated cuts are valid.

Benchmark CLI: recursive discovery & cut-mode

Layer / File(s) Summary
CLI Help & Parsing
benchmarks/linear_programming/run_mps_files.sh
Adds --recursive and --cut-mode options to usage/help and argument parsing; initializes RECURSIVE=false, CUT_MODE=default, and validates allowed cut-mode values.
Model Discovery
benchmarks/linear_programming/run_mps_files.sh
When --recursive=true uses find to locate .mps/.MPS/.SIF files; otherwise retains non-recursive listing.
Worker Command Wiring
benchmarks/linear_programming/run_mps_files.sh
Applies --mip-...-cuts flags in per-worker command construction according to CUT_MODE (default, no-cuts, flow-cover-only).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add flow cover cuts at root' directly and clearly summarizes the main feature addition, accurately reflecting the primary change of implementing flow-cover cut generation.
Description check ✅ Passed The description is well-related to the changeset, providing implementation details about flow-cover cut generation and reporting benchmark improvements.
Linked Issues check ✅ Passed The PR fully addresses issue #1065 by implementing c-MIR flow-cover cut generation for 0-1 single-node-flow relaxations as requested.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing flow-cover cuts: constants, solver settings, cut generation logic, benchmarking scripts, and related tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

@hlinsen hlinsen changed the title Flow cover cuts Add flow cover cuts at root May 5, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@benchmarks/linear_programming/run_mps_files.sh`:
- Around line 359-364: The recursive file collection can produce duplicate
basenames that cause log files to be overwritten; update the code that
constructs per-model log filenames to include a path-unique component (e.g., use
the file's path relative to MPS_DIR with slashes replaced by safe separators or
use dirname+basename) so each entry in mps_files yields a unique log name when
RECURSIVE=true and --write-log-file is enabled; locate where run_mps_files.sh
iterates over the mps_files array and replace uses of basename (or "$(basename
"$file").log") with a deterministic path-derived filename (for example
"${file#$MPS_DIR/}" with non-alphanumerics normalized) to avoid clobbering logs.

In `@cpp/src/cuts/cuts.cpp`:
- Around line 951-953: The hardcoded thresholds tol and bound_tol in cuts.cpp
(const f_t tol = 1e-6; const f_t bound_tol = 1e-8;) should be replaced with the
solver's configured numerical tolerances; find where these are used (flow-cover
acceptance and downstream cut cutoff computations) and read the solver/LP
feasibility and bound tolerances instead of the literals (e.g., use the
simplex/LP object's feasibility epsilon and bound epsilon parameters). Update
all dependent comparisons that currently use tol or bound_tol so they derive
from those solver tolerances (and propagate scaling if needed), and add a short
comment noting these come from the solver's tolerance settings rather than being
hardcoded.
- Around line 3881-3888: The code assumes slack_map_[i] is valid and indexes
lp.lower/upper unconditionally; when slack_map_[i] == -1 (rows with
slack_count==0) this causes out-of-bounds access. Fix by guarding the lookup:
check if slack_map_[i] >= 0 before accessing lp.lower[slack_map_[i]] and
lp.upper[slack_map_[i]]; if negative, set slack_lower/slack_upper (and derived
sigma_slack_lower/sigma_slack_upper) to safe defaults (e.g., 0) or compute them
in a way consistent with slack_coeff_i==0 so the rest of the logic remains
correct. Apply the same guarded check and handling in the mirrored block around
lines 3981-3988 as well.

In `@cpp/tests/mip/cuts_test.cu`:
- Around line 1482-1498: The test utilities access fixed indices without
validating vector size; update single_node_flow_fractional_solution to check
num_cols >= 6 (or clamp/resize/throw) before writing xstar[0]..xstar[5], and
update single_node_flow_y_feasible to verify y.size() >= 3 before reading y[0],
y[1], y[2] (or return false/assert/throw with a clear message). Use the function
names single_node_flow_fractional_solution and single_node_flow_y_feasible and
include a clear failure mode (assert or exception) so an input-shape mismatch
yields a deterministic test failure rather than undefined behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 21e0b82f-501c-4310-a5c3-f4986b9a676f

📥 Commits

Reviewing files that changed from the base of the PR and between a4de253 and 462b9a1.

📒 Files selected for processing (9)
  • benchmarks/linear_programming/run_mps_files.sh
  • cpp/include/cuopt/linear_programming/constants.h
  • cpp/include/cuopt/linear_programming/mip/solver_settings.hpp
  • cpp/src/cuts/cuts.cpp
  • cpp/src/cuts/cuts.hpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/mip_heuristics/solver.cu
  • cpp/tests/mip/cuts_test.cu

Comment on lines +359 to +364
if [ "$RECURSIVE" = true ]; then
mapfile -t mps_files < <(find "$MPS_DIR" -type f \( -name "*.mps" -o -name "*.MPS" -o -name "*.SIF" \) | sort)
else
# Gather .mps/.MPS and .SIF files in the directory
mapfile -t mps_files < <(ls "$MPS_DIR"/*.mps "$MPS_DIR"/*.MPS "$MPS_DIR"/*.SIF 2>/dev/null)
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Recursive mode can clobber per-model log files.

With --recursive, two models from different subdirectories can share the same basename, but the worker still writes logs to $(basename ...).log. The later run overwrites the earlier one, so benchmark results become incomplete whenever --write-log-file is enabled.

Suggested fix
-        if [ "$WRITE_LOG_FILE" = true ]; then
-            args="$args --log-file $OUTPUT_DIR/$(basename "${mps_file%.mps}").log"
-        fi
+        if [ "$WRITE_LOG_FILE" = true ]; then
+            rel_model_path="${mps_file#$MPS_DIR/}"
+            safe_model_name="${rel_model_path//\//__}"
+            safe_model_name="${safe_model_name%.mps}"
+            safe_model_name="${safe_model_name%.MPS}"
+            safe_model_name="${safe_model_name%.SIF}"
+            args="$args --log-file $OUTPUT_DIR/${safe_model_name}.log"
+        fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/linear_programming/run_mps_files.sh` around lines 359 - 364, The
recursive file collection can produce duplicate basenames that cause log files
to be overwritten; update the code that constructs per-model log filenames to
include a path-unique component (e.g., use the file's path relative to MPS_DIR
with slashes replaced by safe separators or use dirname+basename) so each entry
in mps_files yields a unique log name when RECURSIVE=true and --write-log-file
is enabled; locate where run_mps_files.sh iterates over the mps_files array and
replace uses of basename (or "$(basename "$file").log") with a deterministic
path-derived filename (for example "${file#$MPS_DIR/}" with non-alphanumerics
normalized) to avoid clobbering logs.

Comment thread cpp/src/cuts/cuts.cpp Outdated
Comment thread cpp/src/cuts/cuts.cpp
Comment on lines +1482 to +1498
std::vector<double> single_node_flow_fractional_solution(int num_cols)
{
std::vector<double> xstar(num_cols, 0.0);
xstar[0] = 1.0;
xstar[1] = 2.0 / 3.0;
xstar[2] = 1.0;
xstar[3] = 3.0;
xstar[4] = 4.0;
xstar[5] = 3.0;
return xstar;
}

bool single_node_flow_y_feasible(const std::vector<double>& y)
{
const double activity = y[0] + y[1] - y[2];
return activity <= 4.0 + 1e-8;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard fixed-index writes/reads to avoid UB if LP column layout changes.

Line 1485–1490 and Line 1496 assume minimum vector sizes (6 and 3) without checks. If convert_user_problem output shape changes, this can become out-of-bounds and make the test crash/flap instead of failing with a clear assertion.

Suggested patch
 std::vector<double> single_node_flow_fractional_solution(int num_cols)
 {
+  cuopt_assert(num_cols >= 6, "Expected at least 6 columns for x0..x2,y0..y2");
   std::vector<double> xstar(num_cols, 0.0);
   xstar[0] = 1.0;
   xstar[1] = 2.0 / 3.0;
   xstar[2] = 1.0;
   xstar[3] = 3.0;
   xstar[4] = 4.0;
   xstar[5] = 3.0;
   return xstar;
 }

 bool single_node_flow_y_feasible(const std::vector<double>& y)
 {
+  cuopt_assert(y.size() == 3, "Expected exactly 3 flow variables");
   const double activity = y[0] + y[1] - y[2];
   return activity <= 4.0 + 1e-8;
 }

As per coding guidelines, **/*.{cpp,cuh,cu,hpp,py}: Flag missing input validation at library and server boundaries.

Also applies to: 1579-1581

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/tests/mip/cuts_test.cu` around lines 1482 - 1498, The test utilities
access fixed indices without validating vector size; update
single_node_flow_fractional_solution to check num_cols >= 6 (or
clamp/resize/throw) before writing xstar[0]..xstar[5], and update
single_node_flow_y_feasible to verify y.size() >= 3 before reading y[0], y[1],
y[2] (or return false/assert/throw with a clear message). Use the function names
single_node_flow_fractional_solution and single_node_flow_y_feasible and include
a clear failure mode (assert or exception) so an input-shape mismatch yields a
deterministic test failure rather than undefined behavior.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/src/cuts/cuts.cpp`:
- Around line 1408-1410: Replace the two different hardcoded tolerances used
when checking SNF arc feasibility with a single configurable tolerance from
solver settings (e.g., use the solver's numeric tolerance parameter) and apply
it consistently to both the selection check (where best->arc.y_value and
best->arc.y_value - best->arc.u * best->arc.x_value are tested) and the
revalidation loop that currently uses a tighter literal; update the checks to
reference that single tolerance symbol and add a short inline
comment/documentation string noting the tolerance purpose so it isn't left as a
magic literal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b8908d32-ea99-4ecb-a47d-5b5280153d6b

📥 Commits

Reviewing files that changed from the base of the PR and between af621c7 and e0db214.

📒 Files selected for processing (2)
  • cpp/src/cuts/cuts.cpp
  • cpp/src/cuts/cuts.hpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • cpp/src/cuts/cuts.hpp

Comment thread cpp/src/cuts/cuts.cpp Outdated
Comment thread cpp/src/cuts/cuts.cpp Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request New feature or request non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA] Flow cover cuts

2 participants