Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,13 @@ find_package(CUDSS REQUIRED)
# ##################################################################################################
# - gRPC and Protobuf setup (REQUIRED) ------------------------------------------------------------

if(NOT TARGET OpenSSL::SSL)
find_package(OpenSSL CONFIG QUIET)
if(NOT OpenSSL_FOUND AND NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
endif()
endif()

# gRPC is required for this branch - it provides remote execution features
# gRPC can come from either:
# - an installed CMake package (gRPCConfig.cmake), or
Expand Down
1 change: 0 additions & 1 deletion cpp/cuopt_cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ int run_single_file(const std::string& file_path,
std::make_unique<cuopt::linear_programming::cpu_optimization_problem_t<int, double>>();
}

// Populate the problem from MPS data model
cuopt::linear_programming::populate_from_mps_data_model(problem_interface.get(), mps_data_model);

const bool is_mip = (problem_interface->get_problem_category() ==
Expand Down
1 change: 1 addition & 0 deletions cpp/include/cuopt/linear_programming/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
#define CUOPT_PRESOLVE_FILE "presolve_file"
#define CUOPT_RANDOM_SEED "random_seed"
#define CUOPT_PDLP_PRECISION "pdlp_precision"
#define CUOPT_SC_BIG_M "sc_big_m"

#define CUOPT_MIP_HYPER_HEURISTIC_POPULATION_SIZE "mip_hyper_heuristic_population_size"
#define CUOPT_MIP_HYPER_HEURISTIC_NUM_CPUFJ_THREADS "mip_hyper_heuristic_num_cpufj_threads"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class mip_solver_settings_t {

f_t time_limit = std::numeric_limits<f_t>::infinity();
f_t work_limit = std::numeric_limits<f_t>::infinity();
f_t sc_big_m = f_t(1e5);
i_t node_limit = std::numeric_limits<i_t>::max();
bool heuristics_only = false;
i_t reliability_branching = -1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

namespace cuopt::linear_programming {

enum class var_t { CONTINUOUS = 0, INTEGER };
enum class var_t { CONTINUOUS = 0, INTEGER, SEMI_CONTINUOUS };
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

Update the remote/grpc enum mappers in the same change.

Line 23 adds var_t::SEMI_CONTINUOUS, but cpp/src/grpc/grpc_problem_mapper.cpp:108-119, cpp/src/grpc/grpc_problem_mapper.cpp:131-141, and cpp/src/grpc/grpc_problem_mapper.cpp:638-648 still only map CONTINUOUS/INTEGER and throw in the default branch. Any remote solve or chunked transfer that carries an SC variable will still fail at runtime.

🤖 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_interface.hpp` at
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).

enum class problem_category_t : int8_t { LP = 0, MIP = 1, IP = 2 };

template <typename i_t, typename f_t>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,14 @@ void populate_from_mps_data_model(optimization_problem_interface_t<i_t, f_t>* pr
if (!char_variable_types.empty()) {
std::vector<var_t> enum_variable_types(char_variable_types.size());
for (size_t i = 0; i < char_variable_types.size(); ++i) {
enum_variable_types[i] = (char_variable_types[i] == 'I' || char_variable_types[i] == 'B')
? var_t::INTEGER
: var_t::CONTINUOUS;
const char c = char_variable_types[i];
if (c == 'I' || c == 'B') {
enum_variable_types[i] = var_t::INTEGER;
} else if (c == 'S') {
enum_variable_types[i] = var_t::SEMI_CONTINUOUS;
} else {
enum_variable_types[i] = var_t::CONTINUOUS;
}
}
problem->set_variable_types(enum_variable_types.data(), enum_variable_types.size());
// Problem category (LP/MIP/IP) is auto-detected by set_variable_types
Expand Down Expand Up @@ -253,7 +258,9 @@ void populate_from_data_model_view(optimization_problem_interface_t<i_t, f_t>* p
data_model->get_variable_types().data() + data_model->get_variable_types().size(),
enum_variable_types.begin(),
[](const auto val) -> var_t {
return (val == 'I' || val == 'B') ? var_t::INTEGER : var_t::CONTINUOUS;
if (val == 'I' || val == 'B') return var_t::INTEGER;
if (val == 'S') return var_t::SEMI_CONTINUOUS;
return var_t::CONTINUOUS;
Comment on lines +261 to +263
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 | 🟡 Minor

🧩 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 -60

Repository: 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.cpp

Repository: 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.cpp

Repository: 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).

});
problem->set_variable_types(enum_variable_types.data(), enum_variable_types.size());
// Problem category (LP/MIP/IP) is auto-detected by set_variable_types
Expand Down
35 changes: 27 additions & 8 deletions cpp/libmps_parser/src/mps_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,14 @@ BoundType convert(std::string_view str)
return LowerBoundIntegerVariable;
} else if (str == "UI") {
return UpperBoundIntegerVariable;
} else if (str == "LC") {
return SemiContiniousVariable;
} else if (str == "SC" || str == "LC") {
return SemiContinuousVariable;
} else {
mps_parser_expects(false,
error_type_t::ValidationError,
"Invalid variable bound type found in BOUNDS section! Bound type=%s",
std::string(str).c_str());
return SemiContiniousVariable;
return SemiContinuousVariable;
}
}

Expand Down Expand Up @@ -1378,6 +1378,7 @@ void mps_parser_t<i_t, f_t>::read_bound_and_value(std::string_view line,
switch (bound_type) {
case LowerBound: {
variable_lower_bounds[var_id] = get_numerical_bound(line, start);
lower_bounds_defined_for_var_id.insert(var_id);
break;
}
case UpperBound: {
Expand All @@ -1394,15 +1395,18 @@ void mps_parser_t<i_t, f_t>::read_bound_and_value(std::string_view line,
const f_t val = get_numerical_bound(line, start);
variable_lower_bounds[var_id] = val;
variable_upper_bounds[var_id] = val;
lower_bounds_defined_for_var_id.insert(var_id);
break;
}
case Free: {
variable_lower_bounds[var_id] = -std::numeric_limits<f_t>::infinity();
variable_upper_bounds[var_id] = +std::numeric_limits<f_t>::infinity();
lower_bounds_defined_for_var_id.insert(var_id);
break;
}
case LowerBoundNegInf:
variable_lower_bounds[var_id] = -std::numeric_limits<f_t>::infinity();
lower_bounds_defined_for_var_id.insert(var_id);
break;
case UpperBoundInf:
variable_upper_bounds[var_id] = +std::numeric_limits<f_t>::infinity();
Expand All @@ -1411,6 +1415,7 @@ void mps_parser_t<i_t, f_t>::read_bound_and_value(std::string_view line,
variable_lower_bounds[var_id] = 0;
variable_upper_bounds[var_id] = 1;
var_types[var_id] = 'I';
lower_bounds_defined_for_var_id.insert(var_id);
break;
case LowerBoundIntegerVariable:
// CPLEX MPS file references seems to imply that integer variables default to an upper bound
Expand All @@ -1420,6 +1425,7 @@ void mps_parser_t<i_t, f_t>::read_bound_and_value(std::string_view line,
}
variable_lower_bounds[var_id] = get_numerical_bound(line, start);
var_types[var_id] = 'I';
lower_bounds_defined_for_var_id.insert(var_id);
break;
case UpperBoundIntegerVariable:
variable_upper_bounds[var_id] = get_numerical_bound(line, start);
Expand All @@ -1431,11 +1437,24 @@ void mps_parser_t<i_t, f_t>::read_bound_and_value(std::string_view line,
}
var_types[var_id] = 'I';
break;
case SemiContiniousVariable:
mps_parser_expects(false,
error_type_t::ValidationError,
"Unsupported semi continous bound type found! Line=%s",
std::string(line).c_str());
case SemiContinuousVariable:
// SC bound type: value is the upper bound U.
if (fixed_mps_format) {
const auto maybe_value =
start == std::string_view::npos ? std::string_view{} : trim(line.substr(start, 12));
variable_upper_bounds[var_id] = maybe_value.empty() ? +std::numeric_limits<f_t>::infinity()
: get_numerical_bound(line, start);
} else {
const auto maybe_value =
start == std::string_view::npos ? std::string_view{} : trim(line.substr(start));
if (!maybe_value.empty()) {
std::stringstream ss{std::string(maybe_value)};
ss >> variable_upper_bounds[var_id];
} else {
variable_upper_bounds[var_id] = +std::numeric_limits<f_t>::infinity();
}
}
var_types[var_id] = 'S';
break;
default:
mps_parser_expects(false,
Expand Down
3 changes: 2 additions & 1 deletion cpp/libmps_parser/src/mps_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ enum BoundType {
BinaryVariable,
LowerBoundIntegerVariable,
UpperBoundIntegerVariable,
SemiContiniousVariable,
SemiContinuousVariable,
}; // enum BoundType

/**
Expand Down Expand Up @@ -135,6 +135,7 @@ class mps_parser_t {
std::unordered_map<std::string, i_t> var_names_map{};
std::unordered_set<std::string> ignored_objective_names{};
std::unordered_set<i_t> bounds_defined_for_var_id{};
std::unordered_set<i_t> lower_bounds_defined_for_var_id{};
static constexpr f_t unset_range_value = std::numeric_limits<f_t>::infinity();

/* Reads an MPS input file into a buffer.
Expand Down
28 changes: 28 additions & 0 deletions cpp/libmps_parser/tests/mps_parser_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <cmath>
#include <cstdint>
#include <filesystem>
#include <fstream>
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if fstream types/functions are used in the test file
rg -n "ifstream|ofstream|fstream\b" cpp/libmps_parser/tests/mps_parser_test.cpp

Repository: NVIDIA/cuopt

Length of output: 77


Remove unused <fstream> include at line 19.

The <fstream> header was added but is never used in the test file; all file operations are performed via std::filesystem. This include should be removed to keep dependencies minimal.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cpp/libmps_parser/tests/mps_parser_test.cpp` at 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.

#include <sstream>
#include <string>
#include <vector>
Expand Down Expand Up @@ -422,6 +423,33 @@ TEST(mps_bounds, upper_inf_var_bound)
EXPECT_EQ(std::numeric_limits<double>::infinity(), mps.variable_upper_bounds[1]);
}

TEST(mps_bounds, semi_continuous_var_bounds_from_dataset)
{
struct Case {
const char* file;
int n_vars;
double lower;
double upper;
};
const std::vector<Case> cases = {
{"mip/sc_standard.mps", 2, 2.0, 10.0},
{"mip/sc_lb_zero.mps", 2, 0.0, 10.0},
{"mip/sc_no_ub.mps", 2, 2.0, 1e30},
};

for (const auto& c : cases) {
SCOPED_TRACE(c.file);
auto mps = read_from_mps(c.file, false);

ASSERT_EQ(c.n_vars, static_cast<int>(mps.var_types.size()));
EXPECT_EQ('S', mps.var_types[0]);
ASSERT_EQ(c.n_vars, static_cast<int>(mps.variable_lower_bounds.size()));
ASSERT_EQ(c.n_vars, static_cast<int>(mps.variable_upper_bounds.size()));
EXPECT_DOUBLE_EQ(c.lower, mps.variable_lower_bounds[0]);
EXPECT_DOUBLE_EQ(c.upper, mps.variable_upper_bounds[0]);
}
}

TEST(mps_ranges, fixed_ranges)
{
std::string file = "linear_programming/good-mps-fixed-ranges.mps";
Expand Down
1 change: 1 addition & 0 deletions cpp/src/math_optimization/solver_settings.cu
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ solver_settings_t<i_t, f_t>::solver_settings_t() : pdlp_settings(), mip_settings
{CUOPT_MIP_HYPER_HEURISTIC_INITIAL_INFEASIBILITY_WEIGHT, &mip_settings.heuristic_params.initial_infeasibility_weight, f_t(1e-9), std::numeric_limits<f_t>::infinity(), f_t(1000.0), "constraint violation penalty seed"},
{CUOPT_MIP_HYPER_HEURISTIC_RELAXED_LP_TIME_LIMIT, &mip_settings.heuristic_params.relaxed_lp_time_limit, f_t(1e-9), std::numeric_limits<f_t>::infinity(), f_t(1.0), "base relaxed LP time cap in heuristics"},
{CUOPT_MIP_HYPER_HEURISTIC_RELATED_VARS_TIME_LIMIT, &mip_settings.heuristic_params.related_vars_time_limit, f_t(1e-9), std::numeric_limits<f_t>::infinity(), f_t(30.0), "time for related-variable structure build"},
{CUOPT_SC_BIG_M, &mip_settings.sc_big_m, f_t(1.0), std::numeric_limits<f_t>::infinity(), f_t(1e5), "big-M value for semi-continuous variables with no finite upper bound"},
};

// Int parameters
Expand Down
1 change: 1 addition & 0 deletions cpp/src/mip_heuristics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ set(MIP_NON_LP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/local_search/line_segment_search/line_segment_search.cu
${CMAKE_CURRENT_SOURCE_DIR}/presolve/bounds_presolve.cu
${CMAKE_CURRENT_SOURCE_DIR}/presolve/bounds_update_data.cu
${CMAKE_CURRENT_SOURCE_DIR}/presolve/semi_continuous.cu
${CMAKE_CURRENT_SOURCE_DIR}/presolve/conditional_bound_strengthening.cu
${CMAKE_CURRENT_SOURCE_DIR}/presolve/multi_probe.cu
${CMAKE_CURRENT_SOURCE_DIR}/presolve/probing_cache.cu
Expand Down
Loading