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
5 changes: 2 additions & 3 deletions src/search/landmarks/landmark_cost_partitioning_algorithms.cc
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,9 @@ lp::LinearProgram OptimalCostPartitioningAlgorithm::build_initial_lp() {
say that the operator's total cost must fall between 0 and the real
operator cost.
*/
lp_constraints.resize(num_rows, lp::LPConstraint(0.0, 0.0));
lp_constraints.resize(num_rows, lp::LPConstraint(lp::Sense::LE, 0.0));
for (size_t op_id = 0; op_id < operator_costs.size(); ++op_id) {
lp_constraints[op_id].set_lower_bound(0);
lp_constraints[op_id].set_upper_bound(operator_costs[op_id]);
lp_constraints[op_id].set_right_hand_side(operator_costs[op_id]);
}

/* Coefficients of constraints will be updated and recreated in each state.
Expand Down
190 changes: 38 additions & 152 deletions src/search/lp/cplex_solver_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

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 rename Sense to LPConstraintSense or ConstraintSense and the function model_sense_to_cplex_sense to objective_sense_to_cplex_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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
cerr << "Unsupported constraint sense: "
<< static_cast<int>(constraint_sense) << endl;
utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR);
ABORT("Unsupported constraint sense: " +
static_cast<int>(constraint_sense));

We usually use ABORT for "this should not be possible", i.e., errors targeted to developers rather than users.

}
}

void CplexSolverInterface::CplexMatrix::assign_column_by_column(
const named_vector::NamedVector<LPConstraint> &constraints, int num_cols) {
coefficients.clear();
Expand Down Expand Up @@ -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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this if make a difference here?

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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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) {
Expand All @@ -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);
Expand All @@ -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());
Expand All @@ -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;
Expand All @@ -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);
}
}

Expand All @@ -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) {
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
Loading
Loading