-
Notifications
You must be signed in to change notification settings - Fork 204
[Issue 1224] Introduce one-sided constraints to the LP interface. #299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -67,30 +67,32 @@ static void freeProblem(CPXENVptr env, CPXLPptr *problem) { | |||||||||||
| CPX_CALL(CPXfreeprob, env, problem); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| static tuple<char, double, double> bounds_to_sense_rhs_range( | ||||||||||||
| double lb, double ub) { | ||||||||||||
| if (lb <= -CPX_INFBOUND && ub >= CPX_INFBOUND) { | ||||||||||||
| // CPLEX does not support <= or >= constraints without bounds. | ||||||||||||
| return {'R', -CPX_INFBOUND, 2 * CPX_INFBOUND}; | ||||||||||||
| } else if (lb <= -CPX_INFBOUND) { | ||||||||||||
| return {'L', ub, 0}; | ||||||||||||
| } else if (ub >= CPX_INFBOUND) { | ||||||||||||
| return {'G', lb, 0}; | ||||||||||||
| } else if (lb == ub) { | ||||||||||||
| return {'E', lb, 0}; | ||||||||||||
| } else { | ||||||||||||
| return {'R', lb, ub - lb}; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| static int sense_to_cplex_sense(LPObjectiveSense sense) { | ||||||||||||
| static int model_sense_to_cplex_sense(LPObjectiveSense sense) { | ||||||||||||
| if (sense == LPObjectiveSense::MINIMIZE) { | ||||||||||||
| return CPX_MIN; | ||||||||||||
| } else { | ||||||||||||
| return CPX_MAX; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| static char constraint_sense_to_cplex_sense(lp::Sense constraint_sense) { | ||||||||||||
| switch (constraint_sense) { | ||||||||||||
| case lp::Sense::LE: | ||||||||||||
| return 'L'; | ||||||||||||
| break; | ||||||||||||
| case lp::Sense::GE: | ||||||||||||
| return 'G'; | ||||||||||||
| break; | ||||||||||||
| case lp::Sense::EQ: | ||||||||||||
| return 'E'; | ||||||||||||
| break; | ||||||||||||
| default: | ||||||||||||
| cerr << "Unsupported constraint sense: " | ||||||||||||
| << static_cast<int>(constraint_sense) << endl; | ||||||||||||
| utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR); | ||||||||||||
|
Comment on lines
+90
to
+92
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We usually use |
||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::CplexMatrix::assign_column_by_column( | ||||||||||||
| const named_vector::NamedVector<LPConstraint> &constraints, int num_cols) { | ||||||||||||
| coefficients.clear(); | ||||||||||||
|
|
@@ -202,53 +204,31 @@ void CplexSolverInterface::CplexColumnsInfo::assign( | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::CplexRowsInfo::assign( | ||||||||||||
| const named_vector::NamedVector<LPConstraint> &constraints, int offset, | ||||||||||||
| bool dense_range_values) { | ||||||||||||
| const named_vector::NamedVector<LPConstraint> &constraints) { | ||||||||||||
| rhs.clear(); | ||||||||||||
| sense.clear(); | ||||||||||||
| range_values.clear(); | ||||||||||||
| range_indices.clear(); | ||||||||||||
|
|
||||||||||||
| int num_rows = constraints.size(); | ||||||||||||
| sense.resize(num_rows); | ||||||||||||
| rhs.resize(num_rows); | ||||||||||||
| if (dense_range_values) { | ||||||||||||
| range_values.resize(num_rows, 0); | ||||||||||||
| if (num_rows > 0) { | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this |
||||||||||||
| sense.resize(num_rows); | ||||||||||||
| rhs.resize(num_rows); | ||||||||||||
| } | ||||||||||||
| for (int row_index = 0; row_index < num_rows; ++row_index) { | ||||||||||||
| const LPConstraint &constraint = constraints[row_index]; | ||||||||||||
| double lb = constraint.get_lower_bound(); | ||||||||||||
| double ub = constraint.get_upper_bound(); | ||||||||||||
| const auto &[sense_value, rhs_value, range_value] = | ||||||||||||
| bounds_to_sense_rhs_range(lb, ub); | ||||||||||||
| sense[row_index] = sense_value; | ||||||||||||
| double rhs_value = constraint.get_right_hand_side(); | ||||||||||||
| lp::Sense constraint_sense = constraint.get_sense(); | ||||||||||||
| char cplex_sense = constraint_sense_to_cplex_sense(constraint_sense); | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if the variable is only needed in the next line, I'd directly assign to the array without a temporary variable. |
||||||||||||
| sense[row_index] = cplex_sense; | ||||||||||||
| rhs[row_index] = rhs_value; | ||||||||||||
| if (sense_value == 'R') { | ||||||||||||
| if (dense_range_values) { | ||||||||||||
| range_values[row_index] = range_value; | ||||||||||||
| } else { | ||||||||||||
| range_values.push_back(range_value); | ||||||||||||
| range_indices.push_back(offset + row_index); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| assert(static_cast<int>(rhs.size()) == constraints.size()); | ||||||||||||
| assert(static_cast<int>(sense.size()) == constraints.size()); | ||||||||||||
| assert(static_cast<int>(range_values.size()) <= constraints.size()); | ||||||||||||
| assert( | ||||||||||||
| (dense_range_values && | ||||||||||||
| (static_cast<int>(range_values.size()) == constraints.size()) && | ||||||||||||
| (range_indices.size() == 0)) || | ||||||||||||
| (!dense_range_values && (range_values.size() == range_indices.size()))); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| CplexSolverInterface::CplexSolverInterface() | ||||||||||||
| : env(nullptr), | ||||||||||||
| problem(nullptr), | ||||||||||||
| is_mip(false), | ||||||||||||
| num_permanent_constraints(0), | ||||||||||||
| num_unsatisfiable_constraints(0), | ||||||||||||
| num_unsatisfiable_temp_constraints(0) { | ||||||||||||
| num_permanent_constraints(0) { | ||||||||||||
| int status = 0; | ||||||||||||
| env = CPXopenCPLEX(&status); | ||||||||||||
| if (status) { | ||||||||||||
|
|
@@ -269,41 +249,6 @@ CplexSolverInterface::~CplexSolverInterface() { | |||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| bool CplexSolverInterface::is_trivially_unsolvable() const { | ||||||||||||
| return num_unsatisfiable_constraints + num_unsatisfiable_temp_constraints > | ||||||||||||
| 0; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::change_constraint_bounds( | ||||||||||||
| int index, double lb, double ub) { | ||||||||||||
| double current_lb = constraint_lower_bounds[index]; | ||||||||||||
| double current_ub = constraint_upper_bounds[index]; | ||||||||||||
| if (current_lb == lb && current_ub == ub) { | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| const auto &[sense, rhs, range] = bounds_to_sense_rhs_range(lb, ub); | ||||||||||||
|
|
||||||||||||
| CPX_CALL(CPXchgsense, env, problem, 1, &index, &sense); | ||||||||||||
| CPX_CALL(CPXchgrhs, env, problem, 1, &index, &rhs); | ||||||||||||
| CPX_CALL(CPXchgrngval, env, problem, 1, &index, &range); | ||||||||||||
|
|
||||||||||||
| if (current_lb > current_ub && lb <= ub) { | ||||||||||||
| if (index < num_permanent_constraints) { | ||||||||||||
| --num_unsatisfiable_constraints; | ||||||||||||
| } else { | ||||||||||||
| --num_unsatisfiable_temp_constraints; | ||||||||||||
| } | ||||||||||||
| } else if (current_lb <= current_ub && lb > ub) { | ||||||||||||
| if (index < num_permanent_constraints) { | ||||||||||||
| ++num_unsatisfiable_constraints; | ||||||||||||
| } else { | ||||||||||||
| ++num_unsatisfiable_temp_constraints; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| constraint_lower_bounds[index] = lb; | ||||||||||||
| constraint_upper_bounds[index] = ub; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::load_problem(const LinearProgram &lp) { | ||||||||||||
| if (problem) { | ||||||||||||
| freeProblem(env, &problem); | ||||||||||||
|
|
@@ -319,36 +264,22 @@ void CplexSolverInterface::load_problem(const LinearProgram &lp) { | |||||||||||
| const named_vector::NamedVector<LPConstraint> &constraints = | ||||||||||||
| lp.get_constraints(); | ||||||||||||
| num_permanent_constraints = constraints.size(); | ||||||||||||
| num_unsatisfiable_constraints = 0; | ||||||||||||
| for (const LPConstraint &constraint : constraints) { | ||||||||||||
| if (constraint.get_lower_bound() > constraint.get_upper_bound()) { | ||||||||||||
| ++num_unsatisfiable_constraints; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| matrix.assign_column_by_column(constraints, variables.size()); | ||||||||||||
| columns.assign(variables); | ||||||||||||
| rows.assign(constraints); | ||||||||||||
| CPX_CALL( | ||||||||||||
| CPXcopylp, env, problem, variables.size(), constraints.size(), | ||||||||||||
| sense_to_cplex_sense(lp.get_sense()), columns.get_objective(), | ||||||||||||
| model_sense_to_cplex_sense(lp.get_sense()), columns.get_objective(), | ||||||||||||
| rows.get_rhs(), rows.get_sense(), matrix.get_starts(), | ||||||||||||
| matrix.get_counts(), matrix.get_indices(), matrix.get_coefficients(), | ||||||||||||
| columns.get_lb(), columns.get_ub(), rows.get_range_values()); | ||||||||||||
| columns.get_lb(), columns.get_ub(), nullptr); | ||||||||||||
|
|
||||||||||||
| if (is_mip) { | ||||||||||||
| CPX_CALL(CPXcopyctype, env, problem, columns.get_type()); | ||||||||||||
| } else { | ||||||||||||
| assert(CPXgetprobtype(env, problem) == CPXPROB_LP); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| constraint_lower_bounds.clear(); | ||||||||||||
| constraint_upper_bounds.clear(); | ||||||||||||
| for (const LPConstraint &constraint : constraints) { | ||||||||||||
| constraint_lower_bounds.push_back(constraint.get_lower_bound()); | ||||||||||||
| constraint_upper_bounds.push_back(constraint.get_upper_bound()); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // Optionally set names. | ||||||||||||
| if (!lp.get_objective_name().empty()) { | ||||||||||||
| CPX_CALL(CPXchgprobname, env, problem, lp.get_objective_name().c_str()); | ||||||||||||
|
|
@@ -369,14 +300,8 @@ void CplexSolverInterface::load_problem(const LinearProgram &lp) { | |||||||||||
|
|
||||||||||||
| void CplexSolverInterface::add_temporary_constraints( | ||||||||||||
| const named_vector::NamedVector<LPConstraint> &constraints) { | ||||||||||||
| for (const LPConstraint &constraint : constraints) { | ||||||||||||
| if (constraint.get_lower_bound() > constraint.get_upper_bound()) { | ||||||||||||
| ++num_unsatisfiable_temp_constraints; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| matrix.assign_row_by_row(constraints); | ||||||||||||
| rows.assign(constraints, get_num_constraints(), false); | ||||||||||||
| rows.assign(constraints); | ||||||||||||
| CplexNameData row_names(constraints); | ||||||||||||
| // CPXaddrows can add new variables as well, but we do not want any. | ||||||||||||
| static const int num_extra_columns = 0; | ||||||||||||
|
|
@@ -386,32 +311,13 @@ void CplexSolverInterface::add_temporary_constraints( | |||||||||||
| matrix.get_num_nonzeros(), rows.get_rhs(), rows.get_sense(), | ||||||||||||
| matrix.get_starts(), matrix.get_indices(), matrix.get_coefficients(), | ||||||||||||
| extra_column_names, row_names.get_names()); | ||||||||||||
|
|
||||||||||||
| /* | ||||||||||||
| If there are any ranged rows, we have to set up their ranges with a | ||||||||||||
| separate call. | ||||||||||||
| */ | ||||||||||||
| if (rows.get_num_ranged_rows() > 0) { | ||||||||||||
| CPX_CALL( | ||||||||||||
| CPXchgrngval, env, problem, rows.get_num_ranged_rows(), | ||||||||||||
| rows.get_range_indices(), rows.get_range_values()); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| for (const LPConstraint &constraint : constraints) { | ||||||||||||
| constraint_lower_bounds.push_back(constraint.get_lower_bound()); | ||||||||||||
| constraint_upper_bounds.push_back(constraint.get_upper_bound()); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::clear_temporary_constraints() { | ||||||||||||
| int start = num_permanent_constraints; | ||||||||||||
| int end = get_num_constraints() - 1; | ||||||||||||
| if (start <= end) { | ||||||||||||
| CPX_CALL(CPXdelrows, env, problem, start, end); | ||||||||||||
| num_unsatisfiable_temp_constraints = 0; | ||||||||||||
|
|
||||||||||||
| constraint_lower_bounds.resize(num_permanent_constraints); | ||||||||||||
| constraint_upper_bounds.resize(num_permanent_constraints); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
|
|
@@ -434,12 +340,14 @@ void CplexSolverInterface::set_objective_coefficient( | |||||||||||
| CPX_CALL(CPXchgobj, env, problem, 1, &index, &coefficient); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::set_constraint_lower_bound(int index, double bound) { | ||||||||||||
| change_constraint_bounds(index, bound, constraint_upper_bounds[index]); | ||||||||||||
| void CplexSolverInterface::set_constraint_rhs( | ||||||||||||
| int index, double right_hand_side) { | ||||||||||||
| CPX_CALL(CPXchgrhs, env, problem, 1, &index, &right_hand_side); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::set_constraint_upper_bound(int index, double bound) { | ||||||||||||
| change_constraint_bounds(index, constraint_lower_bounds[index], bound); | ||||||||||||
| void CplexSolverInterface::set_constraint_sense(int index, lp::Sense sense) { | ||||||||||||
| char cplex_sense = constraint_sense_to_cplex_sense(sense); | ||||||||||||
| CPX_CALL(CPXchgsense, env, problem, 1, &index, &cplex_sense); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::set_variable_lower_bound(int index, double bound) { | ||||||||||||
|
|
@@ -457,33 +365,20 @@ void CplexSolverInterface::set_mip_gap(double gap) { | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::solve() { | ||||||||||||
| if (is_trivially_unsolvable()) { | ||||||||||||
| return; | ||||||||||||
| } else if (is_mip) { | ||||||||||||
| if (is_mip) { | ||||||||||||
| CPX_CALL(CPXmipopt, env, problem); | ||||||||||||
| } else { | ||||||||||||
| CPX_CALL(CPXlpopt, env, problem); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::write_lp(const string &filename) const { | ||||||||||||
| if (is_trivially_unsolvable()) { | ||||||||||||
| cerr << "The LP has trivially unsatisfiable constraints that are not " | ||||||||||||
| << "accurately represented in CPLEX. Writing it to a file would " | ||||||||||||
| << "misrepresent the LP." << endl; | ||||||||||||
| utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR); | ||||||||||||
| } | ||||||||||||
| // By not passing in a filetype, we let CPLEX infer it from the filename. | ||||||||||||
| static const char *filetype = nullptr; | ||||||||||||
| CPX_CALL(CPXwriteprob, env, problem, filename.c_str(), filetype); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| void CplexSolverInterface::print_failure_analysis() const { | ||||||||||||
| if (is_trivially_unsolvable()) { | ||||||||||||
| cout << "LP/MIP is infeasible because of a trivially unsatisfiable " | ||||||||||||
| << "constraint" << endl; | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| int status = CPXgetstat(env, problem); | ||||||||||||
| switch (status) { | ||||||||||||
| case CPX_STAT_OPTIMAL: | ||||||||||||
|
|
@@ -515,25 +410,16 @@ void CplexSolverInterface::print_failure_analysis() const { | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| bool CplexSolverInterface::is_infeasible() const { | ||||||||||||
| if (is_trivially_unsolvable()) { | ||||||||||||
| return true; | ||||||||||||
| } | ||||||||||||
| int status = CPXgetstat(env, problem); | ||||||||||||
| return status == CPX_STAT_INFEASIBLE || status == CPXMIP_INFEASIBLE; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| bool CplexSolverInterface::is_unbounded() const { | ||||||||||||
| if (is_trivially_unsolvable()) { | ||||||||||||
| return false; | ||||||||||||
| } | ||||||||||||
| int status = CPXgetstat(env, problem); | ||||||||||||
| return status == CPX_STAT_UNBOUNDED; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| bool CplexSolverInterface::has_optimal_solution() const { | ||||||||||||
| if (is_trivially_unsolvable()) { | ||||||||||||
| return false; | ||||||||||||
| } | ||||||||||||
| int status = CPXgetstat(env, problem); | ||||||||||||
| switch (status) { | ||||||||||||
| case CPX_STAT_OPTIMAL: | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need
lp::here? We should be inside the scope of the namespace already.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, to be consistent with
LPObjectiveSense, maybe we should renameSensetoLPConstraintSenseorConstraintSenseand the functionmodel_sense_to_cplex_sensetoobjective_sense_to_cplex_sense.