Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR implements support for semi-continuous variables across the CUOPT solver stack, including MPS parser enhancements to recognize semi-continuous bound types, a new variable type enum value, reformulation logic that converts semi-continuous variables into binary variables with linking constraints, bounds propagation to derive finite upper bounds with fallback to a configurable big-M parameter, validation checks, and comprehensive test coverage. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
cpp/CMakeLists.txt (1)
292-297: Align OpenSSL discovery with gRPC/protobuf patterns for consistency.The current fallback logic checks
OpenSSL_FOUND/OPENSSL_FOUNDvariables before retrying discovery, but the identical patterns for gRPC and protobuf (lines 303–310) rely solely on target-based checks. This inconsistency is unnecessary—since FOUND variables can be unreliable for confirming target availability, use target re-checks instead.Suggested patch
if(NOT TARGET OpenSSL::SSL) find_package(OpenSSL CONFIG QUIET) - if(NOT OpenSSL_FOUND AND NOT OPENSSL_FOUND) - find_package(OpenSSL REQUIRED) - endif() +endif() +if(NOT TARGET OpenSSL::SSL) + find_package(OpenSSL REQUIRED) endif()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cpp/CMakeLists.txt` around lines 292 - 297, The OpenSSL discovery block should mirror the gRPC/protobuf pattern by relying on target checks instead of FOUND variables; replace the current conditional that tests OpenSSL_FOUND/OPENSSL_FOUND with a second check for the OpenSSL::SSL target. Concretely, keep the initial if(NOT TARGET OpenSSL::SSL) find_package(OpenSSL CONFIG QUIET) call and then, instead of testing OpenSSL_FOUND or OPENSSL_FOUND, re-check if(NOT TARGET OpenSSL::SSL) and only then call find_package(OpenSSL REQUIRED) so discovery consistently uses target-based checks.cpp/include/cuopt/linear_programming/mip/solver_settings.hpp (1)
89-89: Appropriate default for Big-M parameter.The default value of
1e5forsc_big_mis a reasonable choice for Big-M reformulations. Users can adjust this if their problem has larger variable bounds.Consider adding a brief inline comment explaining this parameter's purpose, similar to other settings in this file (e.g.,
node_limit,max_cut_passes).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cpp/include/cuopt/linear_programming/mip/solver_settings.hpp` at line 89, Add a brief inline comment explaining the purpose and guidance for the Big‑M parameter by annotating the sc_big_m setting: clarify that sc_big_m (default f_t(1e5)) is the Big‑M constant used in reformulations, why 1e5 is a reasonable default and that users may increase it for problems with larger variable bounds; place the comment adjacent to the sc_big_m declaration so it matches style of other settings like node_limit and max_cut_passes.cpp/tests/mip/miplib_test.cu (1)
111-136: Add at least one regression where the SC variable is active.Both new MPS cases optimize to
x = 0, so they only cover the inactive branch. Theb = 1linking constraints, positive-interval postsolve path, and fallback-sc_big_mhandling are still untested here.As per coding guidelines, "Add tests for problem transformations: verify correctness of original→transformed→postsolve mappings and index consistency across problem representations".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cpp/tests/mip/miplib_test.cu` around lines 111 - 136, The current test vector in mip_solve::semi_continuous_regressions only includes cases where the semi-continuous (SC) variable is inactive; add at least one MPS test instance where the SC variable is active (e.g., "mip/sc_active.mps") and include the expected objective and non-zero sc_value in the test_instances initializer (sc_result_t) so the loop exercises the active-branch, the b=1 linking constraint, the positive-interval postsolve, and fallback sc_big_m handling; update/verify expectations used by parse_mps, solve_mip, and host_copy in this test so EXPECT_EQ/EXPECT_NEAR assertions validate the original→transformed→postsolve mappings and index consistency for that active instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cpp/include/cuopt/linear_programming/optimization_problem_interface.hpp`:
- Line 23: The new enum value var_t::SEMI_CONTINUOUS was added but the GRPC
mapping logic still only handles CONTINUOUS/INTEGER and throws in the default
branch; update the mappings in cpp/src/grpc/grpc_problem_mapper.cpp to handle
SEMI_CONTINUOUS everywhere it is converted. Specifically, add cases for
var_t::SEMI_CONTINUOUS in the var_t -> proto enum mapper, the proto enum ->
var_t mapper, and the chunked-transfer/remote-solve mapper that currently throws
on unknown values so that SEMI_CONTINUOUS is correctly translated in all three
places (preserve existing switch/return patterns and cover the default branch
accordingly).
In `@cpp/include/cuopt/linear_programming/optimization_problem_utils.hpp`:
- Around line 261-263: The MPS writer currently treats semi-continuous variables
like continuous ones, causing loss of 'S' on round-trips; update the
bounds-serialization logic in the MPS writer (mps_writer) to check
variable_types[j] == 'S' and emit the 'SC' bound marker (and corresponding bound
lines) for those variables just like the integer branch emits "LI"/"UI"—i.e.,
add a branch alongside the existing integer ('I'/'B') handling so
semi-continuous variables are serialized with 'SC' and preserved when parsing
back (refer to variable_types[j] and the bounds-selection code in the writer).
In `@cpp/libmps_parser/tests/mps_parser_test.cpp`:
- Line 19: Remove the unused include directive for <fstream> in the test file:
delete the line containing `#include` <fstream> (the header added at top of
cpp/libmps_parser/tests/mps_parser_test.cpp) since file operations use
std::filesystem and no symbol from <fstream> is referenced.
In `@cpp/src/mip_heuristics/presolve/sc_reformulation.cu`:
- Around line 147-151: The code assigns big_m into U without validating it;
change the fallback logic in the block handling non-finite U so that before
assigning U = big_m you verify std::isfinite(big_m) && (big_m > L) (where L is
the corresponding lower bound used earlier or orig_u), and only then set U and
mark used_fallback_big_m[idx]=1; otherwise keep U as orig_u (or leave it
non-finite and handle as before) and do not mark the fallback used. Update
references around U, orig_u, big_m, used_fallback_big_m and idx to ensure the
fallback flag is only set when the validated big_m was actually applied.
In `@cpp/src/mip_heuristics/solve.cu`:
- Around line 291-304: The in-place semi-continuous reformulation
(detail::reformulate_semi_continuous) mutates op_problem and adds auxiliary
binaries so op_problem.get_n_variables() no longer equals the user's original
variable count; ensure all MIP callback setup and payloads (GET_SOLUTION /
SET_SOLUTION code paths) use the saved n_orig_before_sc rather than the
reformulated dimension, and trim any solution vectors or callback assignments to
the original prefix of length n_orig_before_sc before invoking user callbacks;
update any callback registration or solution-copy logic to reference
n_orig_before_sc and strip trailing auxiliary variables added by the
reformulation prior to calling user code.
- Around line 291-298: Move creation/starting of the solve timer (the timer_t
instance) to before the semi‑continuous reformulation so the SC phase is counted
against settings.time_limit; specifically, create/start the timer prior to
calling detail::reformulate_semi_continuous(op_problem, settings,
&sc_used_fallback_big_m) (and pass the timer into that routine if it accepts
one) so the time spent in sc_reformulation.cu is budgeted and included in the
reported presolve/solve times; ensure any later references to timer_t (e.g.,
when recording presolve time) still use this same instance so reported timings
include the SC work.
In `@cpp/src/pdlp/utilities/problem_checking.cu`:
- Around line 220-251: The loop in problem_checking.cu indexes var_lb and var_ub
without ensuring bounds were materialized, which can crash when callers set
SEMI_CONTINUOUS types without initializing bounds; before calling
op_problem.get_variable_lower_bounds_host() / get_variable_upper_bounds_host()
or before indexing var_lb[i]/var_ub[i], add a guard that verifies the bounds
vectors are present and sized to match var_types (e.g., check that
op_problem.get_variable_lower_bounds().is_empty() is false or that var_lb.size()
== var_types.size()), and if not, emit a cuopt_expects ValidationError with a
clear message about missing variable bounds for SEMI_CONTINUOUS variables; keep
the existing per-variable SEMI_CONTINUOUS checks (var_types, var_lb, var_ub,
cuopt_expects) but only after this presence/size validation.
---
Nitpick comments:
In `@cpp/CMakeLists.txt`:
- Around line 292-297: The OpenSSL discovery block should mirror the
gRPC/protobuf pattern by relying on target checks instead of FOUND variables;
replace the current conditional that tests OpenSSL_FOUND/OPENSSL_FOUND with a
second check for the OpenSSL::SSL target. Concretely, keep the initial if(NOT
TARGET OpenSSL::SSL) find_package(OpenSSL CONFIG QUIET) call and then, instead
of testing OpenSSL_FOUND or OPENSSL_FOUND, re-check if(NOT TARGET OpenSSL::SSL)
and only then call find_package(OpenSSL REQUIRED) so discovery consistently uses
target-based checks.
In `@cpp/include/cuopt/linear_programming/mip/solver_settings.hpp`:
- Line 89: Add a brief inline comment explaining the purpose and guidance for
the Big‑M parameter by annotating the sc_big_m setting: clarify that sc_big_m
(default f_t(1e5)) is the Big‑M constant used in reformulations, why 1e5 is a
reasonable default and that users may increase it for problems with larger
variable bounds; place the comment adjacent to the sc_big_m declaration so it
matches style of other settings like node_limit and max_cut_passes.
In `@cpp/tests/mip/miplib_test.cu`:
- Around line 111-136: The current test vector in
mip_solve::semi_continuous_regressions only includes cases where the
semi-continuous (SC) variable is inactive; add at least one MPS test instance
where the SC variable is active (e.g., "mip/sc_active.mps") and include the
expected objective and non-zero sc_value in the test_instances initializer
(sc_result_t) so the loop exercises the active-branch, the b=1 linking
constraint, the positive-interval postsolve, and fallback sc_big_m handling;
update/verify expectations used by parse_mps, solve_mip, and host_copy in this
test so EXPECT_EQ/EXPECT_NEAR assertions validate the
original→transformed→postsolve mappings and index consistency for that active
instance.
🪄 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: Pro Plus
Run ID: 1383efd9-9c0c-427d-a6d6-8e526702b927
📒 Files selected for processing (21)
cpp/CMakeLists.txtcpp/cuopt_cli.cppcpp/include/cuopt/linear_programming/constants.hcpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/include/cuopt/linear_programming/optimization_problem_interface.hppcpp/include/cuopt/linear_programming/optimization_problem_utils.hppcpp/libmps_parser/src/mps_parser.cppcpp/libmps_parser/src/mps_parser.hppcpp/libmps_parser/tests/mps_parser_test.cppcpp/src/math_optimization/solver_settings.cucpp/src/mip_heuristics/CMakeLists.txtcpp/src/mip_heuristics/presolve/sc_reformulation.cucpp/src/mip_heuristics/presolve/sc_reformulation.cuhcpp/src/mip_heuristics/solve.cucpp/src/pdlp/optimization_problem.cucpp/src/pdlp/solve.cucpp/src/pdlp/utilities/problem_checking.cucpp/tests/mip/miplib_test.cudatasets/mip/sc_lb_zero.mpsdatasets/mip/sc_no_ub.mpsdatasets/mip/sc_standard.mps
💤 Files with no reviewable changes (1)
- cpp/cuopt_cli.cpp
| if (val == 'I' || val == 'B') return var_t::INTEGER; | ||
| if (val == 'S') return var_t::SEMI_CONTINUOUS; | ||
| return var_t::CONTINUOUS; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if MPS writer handles 'S' type variables for semi-continuous bounds
rg -n "'S'" cpp/libmps_parser/src/mps_writer.cpp
# Check how variable types are handled in the writer
rg -n -C3 "variable_types" cpp/libmps_parser/src/mps_writer.cpp | head -60Repository: NVIDIA/cuopt
Length of output: 3159
🏁 Script executed:
#!/bin/bash
# Verify the exact file and lines mentioned in the review
wc -l cpp/include/cuopt/linear_programming/optimization_problem_utils.hpp
sed -n '255,270p' cpp/include/cuopt/linear_programming/optimization_problem_utils.hpp
# Check for any 'SC' bounds handling in MPS writer
rg -n "'SC'" cpp/libmps_parser/src/mps_writer.cpp
# Check for semi-continuous handling anywhere in the writer
rg -n -i "semi" cpp/libmps_parser/src/mps_writer.cpp
# Get full context of bounds writing section
sed -n '335,370p' cpp/libmps_parser/src/mps_writer.cppRepository: NVIDIA/cuopt
Length of output: 2640
🏁 Script executed:
#!/bin/bash
# Search for any comments about semi-continuous in MPS writer
rg -n -i "semi\|sc bounds\|bound.*type" cpp/libmps_parser/src/mps_writer.cpp | head -20
# Check the MPS data model to understand how SC is meant to be handled
rg -n "'S'" cpp/libmps_parser/src/ -A2 -B2 | head -40
# Look at comments/docs about MPS writer scope
head -50 cpp/libmps_parser/src/mps_writer.cppRepository: NVIDIA/cuopt
Length of output: 1905
Code at lines 261-263 is correct; MPS writer lacks semi-continuous serialization.
The lambda correctly converts the 'S' character to var_t::SEMI_CONTINUOUS, maintaining consistency with the parser's handling and mirroring populate_from_mps_data_model logic. However, the MPS writer does not serialize semi-continuous variables: it only distinguishes integer variables ('I') for bound type selection ("LI"/"UI" vs "LO"/"UP"), treating semi-continuous variables identically to continuous ones. This creates an inconsistency where variables with type 'S' parse correctly but lose their semi-continuous classification on round-trip through the writer. The MPS format supports the 'SC' bound marker for semi-continuous bounds, which should be implemented in cpp/libmps_parser/src/mps_writer.cpp to properly serialize variables with variable_types[j] == 'S'.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cpp/include/cuopt/linear_programming/optimization_problem_utils.hpp` around
lines 261 - 263, The MPS writer currently treats semi-continuous variables like
continuous ones, causing loss of 'S' on round-trips; update the
bounds-serialization logic in the MPS writer (mps_writer) to check
variable_types[j] == 'S' and emit the 'SC' bound marker (and corresponding bound
lines) for those variables just like the integer branch emits "LI"/"UI"—i.e.,
add a branch alongside the existing integer ('I'/'B') handling so
semi-continuous variables are serialized with 'SC' and preserved when parsing
back (refer to variable_types[j] and the bounds-selection code in the writer).
| if (!std::isfinite(U)) { U = orig_u; } | ||
| if (!std::isfinite(U)) { | ||
| U = big_m; | ||
| if (used_fallback_big_m != nullptr) { (*used_fallback_big_m)[idx] = uint8_t{1}; } | ||
| } |
There was a problem hiding this comment.
Validate fallback sc_big_m before using it as U.
When propagation cannot derive a finite bound, this path injects settings.sc_big_m directly into the reformulation. If that value is non-finite or <= L, the new constraint gets an invalid or too-tight U, which can silently make the b = 1 branch impossible or return a truncated-model result without ever tripping the post-solve x ≈ M check on Line 580 of cpp/src/mip_heuristics/solve.cu.
Proposed validation
+#include <cuopt/error.hpp>
...
if (!std::isfinite(U)) {
+ cuopt_expects(std::isfinite(big_m) && big_m > L,
+ cuopt::error_type_t::ValidationError,
+ "sc_big_m must be finite and strictly greater than the semi-continuous lower bound when used as a fallback upper bound");
U = big_m;
if (used_fallback_big_m != nullptr) { (*used_fallback_big_m)[idx] = uint8_t{1}; }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!std::isfinite(U)) { U = orig_u; } | |
| if (!std::isfinite(U)) { | |
| U = big_m; | |
| if (used_fallback_big_m != nullptr) { (*used_fallback_big_m)[idx] = uint8_t{1}; } | |
| } | |
| if (!std::isfinite(U)) { U = orig_u; } | |
| if (!std::isfinite(U)) { | |
| cuopt_expects(std::isfinite(big_m) && big_m > L, | |
| cuopt::error_type_t::ValidationError, | |
| "sc_big_m must be finite and strictly greater than the semi-continuous lower bound when used as a fallback upper bound"); | |
| U = big_m; | |
| if (used_fallback_big_m != nullptr) { (*used_fallback_big_m)[idx] = uint8_t{1}; } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cpp/src/mip_heuristics/presolve/sc_reformulation.cu` around lines 147 - 151,
The code assigns big_m into U without validating it; change the fallback logic
in the block handling non-finite U so that before assigning U = big_m you verify
std::isfinite(big_m) && (big_m > L) (where L is the corresponding lower bound
used earlier or orig_u), and only then set U and mark
used_fallback_big_m[idx]=1; otherwise keep U as orig_u (or leave it non-finite
and handle as before) and do not mark the fallback used. Update references
around U, orig_u, big_m, used_fallback_big_m and idx to ensure the fallback flag
is only set when the validated big_m was actually applied.
| if (!op_problem.get_variable_types().is_empty()) { | ||
| cuopt_expects( | ||
| op_problem.get_variable_types().size() == op_problem.get_objective_coefficients().size(), | ||
| error_type_t::ValidationError, | ||
| "Sizes for vectors related to the variables are not the same. The objective " | ||
| "vector has size %zu and the variable types vector has size %zu.", | ||
| op_problem.get_objective_coefficients().size(), | ||
| op_problem.get_variable_types().size()); | ||
|
|
||
| auto var_types = op_problem.get_variable_types_host(); | ||
| auto var_lb = op_problem.get_variable_lower_bounds_host(); | ||
| auto var_ub = op_problem.get_variable_upper_bounds_host(); | ||
| for (i_t i = 0; i < static_cast<i_t>(var_types.size()); ++i) { | ||
| if (var_types[i] != var_t::SEMI_CONTINUOUS) { continue; } | ||
| cuopt_expects(var_lb[i] > f_t(0), | ||
| error_type_t::ValidationError, | ||
| "Semi-continuous variable must have a strictly positive lower bound, but has " | ||
| "lower bound %g.", | ||
| static_cast<double>(var_lb[i])); | ||
| cuopt_expects(var_ub[i] > f_t(0), | ||
| error_type_t::ValidationError, | ||
| "Semi-continuous variable must have a strictly positive upper bound, but has " | ||
| "upper bound %g.", | ||
| static_cast<double>(var_ub[i])); | ||
| cuopt_expects(var_lb[i] < var_ub[i], | ||
| error_type_t::ValidationError, | ||
| "Semi-continuous variable must satisfy lower bound < upper bound, but has " | ||
| "bounds [%g, %g].", | ||
| static_cast<double>(var_lb[i]), | ||
| static_cast<double>(var_ub[i])); | ||
| } | ||
| } |
There was a problem hiding this comment.
Validate SC bounds are present before indexing the host copies.
Direct API callers can set SEMI_CONTINUOUS types without materializing bounds first. In that case, Lines 234-249 read var_lb[i] and var_ub[i] before a clean validation error is emitted.
Suggested guard
if (!op_problem.get_variable_types().is_empty()) {
cuopt_expects(
op_problem.get_variable_types().size() == op_problem.get_objective_coefficients().size(),
error_type_t::ValidationError,
"Sizes for vectors related to the variables are not the same. The objective "
"vector has size %zu and the variable types vector has size %zu.",
op_problem.get_objective_coefficients().size(),
op_problem.get_variable_types().size());
auto var_types = op_problem.get_variable_types_host();
+ bool has_semi_continuous = false;
+ for (const auto type : var_types) {
+ if (type == var_t::SEMI_CONTINUOUS) {
+ has_semi_continuous = true;
+ break;
+ }
+ }
+ if (has_semi_continuous) {
+ cuopt_expects(op_problem.get_variable_lower_bounds().size() == var_types.size() &&
+ op_problem.get_variable_upper_bounds().size() == var_types.size(),
+ error_type_t::ValidationError,
+ "Semi-continuous variables require explicit lower and upper bounds.");
+ }
auto var_types = op_problem.get_variable_types_host();
auto var_lb = op_problem.get_variable_lower_bounds_host();
auto var_ub = op_problem.get_variable_upper_bounds_host();As per coding guidelines "Validate correct initialization of variable bounds, constraint coefficients, and algorithm state before solving; ensure reset when transitioning between algorithm phases (presolve, simplex, diving, crossover)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cpp/src/pdlp/utilities/problem_checking.cu` around lines 220 - 251, The loop
in problem_checking.cu indexes var_lb and var_ub without ensuring bounds were
materialized, which can crash when callers set SEMI_CONTINUOUS types without
initializing bounds; before calling op_problem.get_variable_lower_bounds_host()
/ get_variable_upper_bounds_host() or before indexing var_lb[i]/var_ub[i], add a
guard that verifies the bounds vectors are present and sized to match var_types
(e.g., check that op_problem.get_variable_lower_bounds().is_empty() is false or
that var_lb.size() == var_types.size()), and if not, emit a cuopt_expects
ValidationError with a clear message about missing variable bounds for
SEMI_CONTINUOUS variables; keep the existing per-variable SEMI_CONTINUOUS checks
(var_types, var_lb, var_ub, cuopt_expects) but only after this presence/size
validation.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
cpp/src/mip_heuristics/presolve/semi_continuous.cu (1)
158-167: Consider using INFO level instead of WARN for the "already contains 0" case.When a semi-continuous variable's interval already contains 0, simplifying it to continuous is mathematically correct and expected behavior, not a warning condition.
CUOPT_LOG_INFOorCUOPT_LOG_DEBUGwould be more appropriate here since this is informational, not indicative of a potential problem.💡 Suggested change
if (!needs_binary[s]) { // 0 already lies in [L, U], so the SC disjunction is just the interval itself. - CUOPT_LOG_WARN( + CUOPT_LOG_INFO( "SC var %d interval [%.6g, %.6g] already contains 0; treating it as continuous", idx, L, orig_u); var_types[idx] = var_t::CONTINUOUS; continue; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cpp/src/mip_heuristics/presolve/semi_continuous.cu` around lines 158 - 167, The log here treats an expected simplification as a warning; replace the CUOPT_LOG_WARN call in the block that checks needs_binary[s] with an informational log (e.g., CUOPT_LOG_INFO or CUOPT_LOG_DEBUG) so that when 0 ∈ [L, orig_u] and you set var_types[idx] = var_t::CONTINUOUS the message reflects normal behavior; update the logging macro referenced around needs_binary[s], idx, L, orig_u accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@cpp/src/mip_heuristics/presolve/semi_continuous.cu`:
- Around line 158-167: The log here treats an expected simplification as a
warning; replace the CUOPT_LOG_WARN call in the block that checks
needs_binary[s] with an informational log (e.g., CUOPT_LOG_INFO or
CUOPT_LOG_DEBUG) so that when 0 ∈ [L, orig_u] and you set var_types[idx] =
var_t::CONTINUOUS the message reflects normal behavior; update the logging macro
referenced around needs_binary[s], idx, L, orig_u accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 09a2b50c-bd81-4d85-9037-c0b7cf1874a3
📒 Files selected for processing (1)
cpp/src/mip_heuristics/presolve/semi_continuous.cu
mlubin
left a comment
There was a problem hiding this comment.
Check the AI comments too, some of them seem relevant.
| // Ensure objective and variable arrays are sized to n_orig | ||
| if (obj_c.empty()) { obj_c.assign(n_orig, f_t(0)); } | ||
|
|
||
| // 5. Count how many SC vars truly need the binary-variable reformulation. |
There was a problem hiding this comment.
Is this something that might already get cleaned up by presolve? It seems like a rare case; probably a modeling error, that we don't need to optimize for.
There was a problem hiding this comment.
I think he needs to know how many of these semicontinuous variables will need binaries.
I do agree that it is probably pretty rare that we are able to convert the semicontinuous to continuous.
There was a problem hiding this comment.
Papilo does not handle semi continuous vars so we have to handle the potential conversion to continuous here. It is a rare case but handled by other solvers and cheap to do.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
cpp/src/mip_heuristics/solve.cu (1)
330-337:⚠️ Potential issue | 🟠 MajorCount SC reformulation against
settings.time_limit.
reformulate_semi_continuous()can still spend real solve time beforetimer_tis created on Line 364. With small time limits, the solve can overrun the user budget before timing starts, and the reported presolve time omits the SC work.Proposed fix
- // Reformulate semi-continuous variables (x = 0 OR L <= x <= U) before Papilo presolve. + auto timer = timer_t(time_limit); + + // Reformulate semi-continuous variables (x = 0 OR L <= x <= U) before Papilo presolve. // Uses GPU bounds propagation to derive tight upper bounds for SC vars with infinite UB. // Track n_orig so that auxiliary binary variables added by reformulation can be stripped // from the solution before returning it to the caller. const i_t n_orig_before_sc = op_problem.get_n_variables(); std::vector<uint8_t> sc_used_fallback_big_m; const bool had_sc = detail::reformulate_semi_continuous(op_problem, settings, &sc_used_fallback_big_m); @@ - auto timer = timer_t(time_limit); if (settings.mip_scaling != CUOPT_MIP_SCALING_OFF) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cpp/src/mip_heuristics/solve.cu` around lines 330 - 337, The semi-continuous reformulation (detail::reformulate_semi_continuous) runs before the presolve timer is created so its time isn't charged to settings.time_limit; move or create the presolve timer (timer_t) before invoking detail::reformulate_semi_continuous (or otherwise start timing there) so the work done by reformulate_semi_continuous is counted against settings.time_limit; ensure the same timer is used for subsequent presolve work and keep tracking of n_orig_before_sc, sc_used_fallback_big_m, op_problem and settings unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cpp/src/grpc/client/solve_remote.cpp`:
- Around line 214-221: The check that disables remote MIP callbacks uses raw
variable enums from cpu_problem.get_variable_types_host() and thus sees
SEMI_CONTINUOUS variables even when solve_mip() would have normalized zero-LB
semi-continuous variables to CONTINUOUS; update the condition so it mirrors that
normalization: compute the effective variable types after applying the same
zero-LB SC normalization (or filter out SEMI_CONTINUOUS entries with lb == 0)
and base has_sc_variables on that normalized view before deciding to clear
mip_callbacks (referencing cpu_problem.get_variable_types_host(),
var_t::SEMI_CONTINUOUS, solve_mip(), and mip_callbacks to locate the logic).
---
Duplicate comments:
In `@cpp/src/mip_heuristics/solve.cu`:
- Around line 330-337: The semi-continuous reformulation
(detail::reformulate_semi_continuous) runs before the presolve timer is created
so its time isn't charged to settings.time_limit; move or create the presolve
timer (timer_t) before invoking detail::reformulate_semi_continuous (or
otherwise start timing there) so the work done by reformulate_semi_continuous is
counted against settings.time_limit; ensure the same timer is used for
subsequent presolve work and keep tracking of n_orig_before_sc,
sc_used_fallback_big_m, op_problem and settings unchanged.
🪄 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: Pro Plus
Run ID: 9e4ef575-e407-4ac0-8487-2bbe053e8a51
📒 Files selected for processing (4)
cpp/include/cuopt/linear_programming/mip/solver_settings.hppcpp/src/grpc/client/solve_remote.cppcpp/src/mip_heuristics/presolve/semi_continuous.cucpp/src/mip_heuristics/solve.cu
| op_relaxed.set_variable_upper_bounds(relaxed_ub.data(), n_orig); | ||
| } | ||
|
|
||
| // 3. Run GPU bounds propagation on the relaxed problem to tighten UBs. |
There was a problem hiding this comment.
Is it worth running all of bound strengthening? Perhaps we should restrict ourselves to just the constraints containing semicontinuous variables and the variables in those constraints.
There was a problem hiding this comment.
I am not sure, if it's time consuming but we can do what you suggested for sure. It will require some problem manipulation or api change in bound strenghtening which I like to avoid to reduce code complexity.
Closes: #1059