Skip to content

Conversation

chris-maes
Copy link
Contributor

@chris-maes chris-maes commented Oct 15, 2025

When we solve the dual, instead of the primal problem, we weren't populating the primal slack variables. As a result crossover had a large initial primal residual and could fail.

This issue appeared when solving the LP relaxation of graph20-80-1rand.

This issue was not present if crossover was disabled.

This PR also removes an unnecessary transpose of the constraint matrix in convert_less_than_to_equal.

Summary by CodeRabbit

  • Improvements

    • Enhanced LP solver with optimized handling of inequality constraints and slack variable augmentation.
    • Added diagnostic logging of primal residuals during crossover operations for improved troubleshooting visibility.
  • Performance

    • Optimized internal computations and memory initialization patterns to reduce overhead.

@chris-maes chris-maes requested a review from a team as a code owner October 15, 2025 18:50
@chris-maes chris-maes requested review from Kh4ster and hlinsen and removed request for a team October 15, 2025 18:50
Copy link

copy-pr-bot bot commented Oct 15, 2025

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@chris-maes chris-maes added bug Something isn't working non-breaking Introduces a non-breaking change labels Oct 15, 2025
@chris-maes chris-maes added this to the 25.12 milestone Oct 15, 2025
@chris-maes chris-maes self-assigned this Oct 15, 2025
@rgsl888prabhu
Copy link
Collaborator

@coderabbitai

Copy link

coderabbitai bot commented Oct 17, 2025

Tip

For best results, initiate chat on the files or code changes.

@rgsl888prabhu, hello! How can I help you with this PR? I can:

  • Review the changes in this PR
  • Answer questions about the code
  • Generate tests or documentation
  • Search for specific patterns or information in the codebase
  • Explain specific parts of the implementation

Please let me know what you'd like me to do!

@rgsl888prabhu
Copy link
Collaborator

Tip

For best results, initiate chat on the files or code changes.

@rgsl888prabhu, hello! How can I help you with this PR? I can:

  • Review the changes in this PR
  • Answer questions about the code
  • Generate tests or documentation
  • Search for specific patterns or information in the codebase
  • Explain specific parts of the implementation

Please let me know what you'd like me to do!

Review

@rgsl888prabhu
Copy link
Collaborator

@coderabbitai review

Copy link

coderabbitai bot commented Oct 17, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

coderabbitai bot commented Oct 17, 2025

Walkthrough

The changes optimize presolve by eliminating an unnecessary CSR row matrix conversion, enhance the dual simplex solver with dynamic slack variable augmentation and primal residual logging during crossover, and standardize thrust-based initialization patterns across MIP components.

Changes

Cohort / File(s) Summary
Dual simplex presolve optimization
cpp/src/dual_simplex/presolve.cpp
Removed temporary CSR row matrix construction and to_compressed_row() call; function now operates directly on problem.A without intermediate conversion.
Dual simplex solver enhancements
cpp/src/dual_simplex/solve.cpp
Added dynamic augmentation of primal LP with slack variables when inequality constraints exist. Recomputes primal residuals after augmentation. Introduces logging of primal residuals before and after artificial variable addition during crossover. Updates state variables (primal_solution.x/z, problem.A, col_start) to accommodate expanded problem structure.
MIP thrust-based initialization
cpp/src/mip/diversity/weights.cuh, cpp/src/mip/problem/presolve_data.cuh, cpp/src/mip/relaxed_lp/lp_state.cuh
Added thrust include headers (thrust/fill.h, thrust/uninitialized_fill.h). Initializes cstr_weights using thrust::fill(policy, ..., 1.0) in weight constructor. Uses thrust::uninitialized_fill for fixed_var_assignment initialization in initialize_var_mapping.

Sequence Diagram

sequenceDiagram
    participant Solver as Dual Simplex Solver
    participant PrimalLP as Primal LP
    participant Slack as Slack Augmentation
    participant Crossover as Crossover Phase

    Solver->>PrimalLP: Check inequality constraints (less_rows > 0)
    alt Inequalities exist
        Solver->>Slack: Augment problem with slack variables
        Slack->>PrimalLP: Expand A, bounds, objective, solution vectors
        Slack->>Slack: Initialize slack variables
        Slack-->>Solver: Decrement less_rows to 0
        Solver->>PrimalLP: Recompute primal residuals
        Solver->>Solver: Log primal residuals (post-augmentation)
    end
    Solver->>Crossover: Enter crossover phase
    Crossover->>Crossover: Log primal residuals (before artificial vars)
    Crossover->>PrimalLP: Append artificial variables
    Crossover->>Crossover: Log primal residuals (after artificial vars)
    Crossover-->>Solver: Crossover complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The change set exhibits mixed complexity: four files contain straightforward thrust include additions and standard initialization patterns, while solve.cpp introduces significant logic modifications (slack augmentation, residual recomputation, logging integration) requiring careful verification of correctness within the simplex algorithm context.

Poem

🐰 A hop through the code, how they weave,
Slacks now augmented with thrust so keen,
Residuals logged at each vital scene,
No temp matrices left to bereave,
The solver leaps forward—efficient, serene!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Crossover dualize fix" is directly related to the primary change in the changeset. According to the PR objectives, the main issue is that primal slack variables were not populated when solving the dual, causing failures during crossover. The title accurately references both "Crossover" (the dual-to-primal transition step) and "dualize" (solving the dual problem instead of primal), which are the core components of the bug being fixed. The title is concise, avoids vague terminology, and should be clearly understandable to developers familiar with LP solver terminology within the cuOpt project context. The secondary fix (removing an unnecessary matrix transpose) is appropriately not emphasized in the title since the main issue drives the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@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: 0

🧹 Nitpick comments (3)
cpp/src/mip/problem/presolve_data.cuh (1)

63-66: Consider using thrust::fill instead of thrust::uninitialized_fill.

After resize() on line 62, the memory is already initialized by rmm::device_uvector. The thrust::uninitialized_fill function is designed for uninitialized memory (placement-new style construction), while thrust::fill is more appropriate for already-initialized memory.

For POD types like f_t (float/double), this likely won't cause runtime issues, but using thrust::fill would be more semantically correct.

-    thrust::uninitialized_fill(handle_ptr->get_thrust_policy(),
-                               fixed_var_assignment.begin(),
-                               fixed_var_assignment.end(),
-                               0.);
+    thrust::fill(handle_ptr->get_thrust_policy(),
+                 fixed_var_assignment.begin(),
+                 fixed_var_assignment.end(),
+                 0.);
cpp/src/dual_simplex/solve.cpp (2)

401-441: Slack augmentation is sound; use numeric_limits and tighten invariants

The construction/population of slacks (s = b − Ax, bounds [0, +∞), z_s = −y) looks correct and fixes the residual issue. Two nits:

  • Replace INFINITY with std::numeric_limits<f_t>::infinity() to avoid header/macro pitfalls and be type‑correct for f_t.
  • Optional: assert invariants around col_start monotonicity after the loop.

Apply includes outside this hunk:

@@
 #include <queue>
 #include <string>
+#include <limits>

And within this block:

-            problem.upper[j]     = INFINITY;
+            problem.upper[j]     = std::numeric_limits<f_t>::infinity();

Optionally, right after setting problem.A.col_start[num_cols]:

+        for (i_t c = 0; c < num_cols; ++c) { assert(problem.A.col_start[c] <= problem.A.col_start[c+1]); }

484-489: Helpful pre-augmentation residual log; minor printf nit nearby

The added pre-artificial residual log is useful. Minor: the nearby “Adding %ld artificial variables” (Line 497) should prefer %zu for size_t to be portable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2312451 and f7cc7a2.

📒 Files selected for processing (5)
  • cpp/src/dual_simplex/presolve.cpp (0 hunks)
  • cpp/src/dual_simplex/solve.cpp (3 hunks)
  • cpp/src/mip/diversity/weights.cuh (1 hunks)
  • cpp/src/mip/problem/presolve_data.cuh (1 hunks)
  • cpp/src/mip/relaxed_lp/lp_state.cuh (1 hunks)
💤 Files with no reviewable changes (1)
  • cpp/src/dual_simplex/presolve.cpp
🔇 Additional comments (6)
cpp/src/mip/relaxed_lp/lp_state.cuh (1)

20-20: LGTM! Missing include added.

The include for thrust/fill.h is necessary since thrust::fill is used in the constructor (lines 35-38). This ensures the code compiles correctly.

cpp/src/mip/problem/presolve_data.cuh (1)

23-23: Include added for thrust::uninitialized_fill usage.

The include is necessary for the function used in initialize_var_mapping.

cpp/src/mip/diversity/weights.cuh (1)

20-20: LGTM! Proper thrust::fill usage for initialization.

The include for thrust/fill.h is correctly added, and the usage of thrust::fill to initialize cstr_weights to 1.0 is appropriate and follows best practices.

Also applies to: 32-32

cpp/src/dual_simplex/solve.cpp (3)

395-401: Row-sense assumption check for slack addition

You treat every non-equality row as a “≤” and add +1 slack. If any rows are “≥”, their slack should enter with −1. Please confirm row senses here or gate by metadata; otherwise the constructed primal may be inconsistent.


443-446: Recomputing residual after slack augmentation: LGTM

Residual on the augmented system is computed correctly as b − A x with the new slack columns.


536-540: Post-augmentation residual log: LGTM

Good to log after appending artificials; this validates the augmentation didn’t worsen feasibility.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants