diff --git a/src/search/landmarks/landmark_cost_partitioning_algorithms.cc b/src/search/landmarks/landmark_cost_partitioning_algorithms.cc index 18a1c0b4bc..b1ea8842a0 100644 --- a/src/search/landmarks/landmark_cost_partitioning_algorithms.cc +++ b/src/search/landmarks/landmark_cost_partitioning_algorithms.cc @@ -190,10 +190,10 @@ 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::LPConstraintSense::LESS_EQUAL, 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. diff --git a/src/search/lp/cplex_solver_interface.cc b/src/search/lp/cplex_solver_interface.cc index 54d77ae2a0..d59c7c181c 100644 --- a/src/search/lp/cplex_solver_interface.cc +++ b/src/search/lp/cplex_solver_interface.cc @@ -9,6 +9,7 @@ #include #include #include +#include using namespace std; @@ -67,23 +68,7 @@ static void freeProblem(CPXENVptr env, CPXLPptr *problem) { CPX_CALL(CPXfreeprob, env, problem); } -static tuple 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 objective_sense_to_cplex_sense(LPObjectiveSense sense) { if (sense == LPObjectiveSense::MINIMIZE) { return CPX_MIN; } else { @@ -91,6 +76,25 @@ static int sense_to_cplex_sense(LPObjectiveSense sense) { } } +static char constraint_sense_to_cplex_sense( + LPConstraintSense constraint_sense) { + switch (constraint_sense) { + case LPConstraintSense::LESS_EQUAL: + return 'L'; + break; + case LPConstraintSense::GREATER_EQUAL: + return 'G'; + break; + case LPConstraintSense::EQUAL: + return 'E'; + break; + default: + ABORT( + "Unsupported constraint sense: " + + to_string(static_cast(constraint_sense))); + } +} + void CplexSolverInterface::CplexMatrix::assign_column_by_column( const named_vector::NamedVector &constraints, int num_cols) { coefficients.clear(); @@ -202,53 +206,27 @@ void CplexSolverInterface::CplexColumnsInfo::assign( } void CplexSolverInterface::CplexRowsInfo::assign( - const named_vector::NamedVector &constraints, int offset, - bool dense_range_values) { + const named_vector::NamedVector &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); - } 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; - 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); - } - } + LPConstraintSense constraint_sense = constraint.get_sense(); + sense[row_index] = constraint_sense_to_cplex_sense(constraint_sense); + rhs[row_index] = constraint.get_right_hand_side(); } assert(static_cast(rhs.size()) == constraints.size()); assert(static_cast(sense.size()) == constraints.size()); - assert(static_cast(range_values.size()) <= constraints.size()); - assert( - (dense_range_values && - (static_cast(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 +247,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,22 +262,15 @@ void CplexSolverInterface::load_problem(const LinearProgram &lp) { const named_vector::NamedVector &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(), + objective_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()); @@ -342,13 +278,6 @@ void CplexSolverInterface::load_problem(const LinearProgram &lp) { 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 +298,8 @@ void CplexSolverInterface::load_problem(const LinearProgram &lp) { void CplexSolverInterface::add_temporary_constraints( const named_vector::NamedVector &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,21 +309,6 @@ 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() { @@ -408,10 +316,6 @@ void CplexSolverInterface::clear_temporary_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 +338,15 @@ 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, LPConstraintSense 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,9 +364,7 @@ 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); @@ -467,23 +372,12 @@ void CplexSolverInterface::solve() { } 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 +409,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: diff --git a/src/search/lp/cplex_solver_interface.h b/src/search/lp/cplex_solver_interface.h index 1c2e171065..ee83b618d1 100644 --- a/src/search/lp/cplex_solver_interface.h +++ b/src/search/lp/cplex_solver_interface.h @@ -31,24 +31,6 @@ class CplexSolverInterface : public SolverInterface { bool is_mip; int num_permanent_constraints; - /* - Our public interface allows using constraints of the form - LB <= expression <= UB - In cases where LB > UB, this constraint is trivially unsatisfiable. - CPLEX does not represent constraints like this and instead uses range - values, where the constraint is represented like this - expression - RNG = LB - where RNG is a variable restricted to take values from 0 to (UB - LB). - If LB > UB, the semantic instead is that RNG takes negative values between - (UB - LB) and 0. This means that in CPLEX, the constraint never is - trivially unsolvable. We still set the range value and the right-hand side - as described above but use negative range values to represent trivially - unsatisfiable constraints. The following two counters track how many such - constraints we have in the permanent and the temporary constraints. - */ - int num_unsatisfiable_constraints; - int num_unsatisfiable_temp_constraints; - /* Matrix data in CPLEX format for loading a new problem. Matrix entries are stored in sparse form: non-zero coefficients are either stored @@ -149,37 +131,17 @@ class CplexSolverInterface : public SolverInterface { class CplexRowsInfo { // Right-hand side value of a row std::vector rhs; - // Sense of a row (Greater or equal, Less or equal, Equal, or Range) + // Sense of a row (Greater or equal, Less or equal, or Equal) std::vector sense; - /* - If the sense of a row is Range, then its value is restricted to the - interval (RHS, RHS + range_value). - */ - std::vector range_values; - /* - In case not all rows specify a sense, this gives the indices of the - rows that are ranged rows. - */ - std::vector range_indices; public: - void assign( - const named_vector::NamedVector &constraints, - int offset = 0, bool dense_range_values = true); + void assign(const named_vector::NamedVector &constraints); + double *get_rhs() { return to_cplex_array(rhs); } char *get_sense() { return to_cplex_array(sense); } - double *get_range_values() { - return to_cplex_array(range_values); - } - int *get_range_indices() { - return to_cplex_array(range_indices); - } - int get_num_ranged_rows() { - return range_indices.size(); - } }; class CplexNameData { @@ -233,21 +195,6 @@ class CplexSolverInterface : public SolverInterface { CplexColumnsInfo columns; CplexRowsInfo rows; std::vector objective_indices; - - /* - We store a copy of the current constraint bounds. We need to know the - current bounds when changing bounds, and accessing them through the CPLEX - interface has a significant overhead. Storing these vectors overlaps with - storing CplexRowsInfo above. The difference is that CplexRowsInfo stores - more information and we reuse it for temporary constraints, while we want - to keep the following vectors always synchronized with the full LP - (permanent and temporary constraints). - */ - std::vector constraint_lower_bounds; - std::vector constraint_upper_bounds; - - bool is_trivially_unsolvable() const; - void change_constraint_bounds(int index, double lb, double ub); public: CplexSolverInterface(); virtual ~CplexSolverInterface() override; @@ -261,8 +208,9 @@ class CplexSolverInterface : public SolverInterface { const std::vector &coefficients) override; virtual void set_objective_coefficient( int index, double coefficient) override; - virtual void set_constraint_lower_bound(int index, double bound) override; - virtual void set_constraint_upper_bound(int index, double bound) override; + virtual void set_constraint_rhs(int index, double right_hand_side) override; + virtual void set_constraint_sense( + int index, LPConstraintSense sense) override; virtual void set_variable_lower_bound(int index, double bound) override; virtual void set_variable_upper_bound(int index, double bound) override; virtual void set_mip_gap(double gap) override; diff --git a/src/search/lp/lp_solver.cc b/src/search/lp/lp_solver.cc index 37b35952d3..b85ff40cd6 100644 --- a/src/search/lp/lp_solver.cc +++ b/src/search/lp/lp_solver.cc @@ -12,6 +12,19 @@ using namespace std; namespace lp { + +ostream &operator<<(ostream &os, LPConstraintSense s) { + switch (s) { + case LPConstraintSense::GREATER_EQUAL: + return os << ">="; + case LPConstraintSense::LESS_EQUAL: + return os << "<="; + case LPConstraintSense::EQUAL: + return os << "=="; + } + return os; +} + void add_lp_solver_option_to_feature(plugins::Feature &feature) { feature.add_option( "lpsolver", @@ -29,8 +42,8 @@ tuple get_lp_solver_arguments_from_options( return make_tuple(opts.get("lpsolver")); } -LPConstraint::LPConstraint(double lower_bound, double upper_bound) - : lower_bound(lower_bound), upper_bound(upper_bound) { +LPConstraint::LPConstraint(LPConstraintSense sense, double right_hand_side) + : sense(sense), right_hand_side(right_hand_side) { } void LPConstraint::clear() { @@ -49,13 +62,6 @@ void LPConstraint::insert(int index, double coefficient) { ostream &LPConstraint::dump( ostream &stream, const LinearProgram *program) const { - double infinity = numeric_limits::infinity(); - if (program) { - infinity = program->get_infinity(); - } - if (lower_bound != -infinity) { - stream << lower_bound << " <= "; - } for (size_t i = 0; i < variables.size(); ++i) { if (i != 0) stream << " + "; @@ -69,11 +75,16 @@ ostream &LPConstraint::dump( } stream << coefficients[i] << " * " << variable_name; } - if (upper_bound != infinity) { - stream << " <= " << upper_bound; - } else if (lower_bound == -infinity) { - stream << " <= infinity"; + stream << get_sense(); + double rhs = get_right_hand_side(); + if (rhs == program->get_infinity()) { + stream << "infinity"; + } else if (rhs == -program->get_infinity()) { + stream << "-infinity"; + } else { + stream << rhs; } + return stream; } @@ -175,12 +186,12 @@ void LPSolver::set_objective_coefficient(int index, double coefficient) { pimpl->set_objective_coefficient(index, coefficient); } -void LPSolver::set_constraint_lower_bound(int index, double bound) { - pimpl->set_constraint_lower_bound(index, bound); +void LPSolver::set_constraint_rhs(int index, double right_hand_side) { + pimpl->set_constraint_rhs(index, right_hand_side); } -void LPSolver::set_constraint_upper_bound(int index, double bound) { - pimpl->set_constraint_upper_bound(index, bound); +void LPSolver::set_constraint_sense(int index, LPConstraintSense sense) { + pimpl->set_constraint_sense(index, sense); } void LPSolver::set_variable_lower_bound(int index, double bound) { diff --git a/src/search/lp/lp_solver.h b/src/search/lp/lp_solver.h index 88baaedb86..27b6cac132 100644 --- a/src/search/lp/lp_solver.h +++ b/src/search/lp/lp_solver.h @@ -31,13 +31,21 @@ std::tuple get_lp_solver_arguments_from_options( class LinearProgram; +enum class LPConstraintSense { + GREATER_EQUAL, // ax >= b + LESS_EQUAL, // ax <= b + EQUAL // ax = b +}; + +std::ostream &operator<<(std::ostream &os, LPConstraintSense s); + class LPConstraint { std::vector variables; std::vector coefficients; - double lower_bound; - double upper_bound; + LPConstraintSense sense; + double right_hand_side; public: - LPConstraint(double lower_bound, double upper_bound); + LPConstraint(LPConstraintSense sense, double right_hand_side); const std::vector &get_variables() const { return variables; @@ -46,17 +54,20 @@ class LPConstraint { return coefficients; } - double get_lower_bound() const { - return lower_bound; + double get_right_hand_side() const { + return right_hand_side; } - void set_lower_bound(double lb) { - lower_bound = lb; + + LPConstraintSense get_sense() const { + return sense; } - double get_upper_bound() const { - return upper_bound; + + void set_right_hand_side(double rhs) { + right_hand_side = rhs; } - void set_upper_bound(double ub) { - upper_bound = ub; + + void set_sense(LPConstraintSense s) { + sense = s; } void clear(); @@ -128,8 +139,8 @@ class LPSolver { void set_objective_coefficients(const std::vector &coefficients); void set_objective_coefficient(int index, double coefficient); - void set_constraint_lower_bound(int index, double bound); - void set_constraint_upper_bound(int index, double bound); + void set_constraint_rhs(int index, double right_hand_side); + void set_constraint_sense(int index, LPConstraintSense sense); void set_variable_lower_bound(int index, double bound); void set_variable_upper_bound(int index, double bound); diff --git a/src/search/lp/solver_interface.h b/src/search/lp/solver_interface.h index 8699a6930c..3ea444599e 100644 --- a/src/search/lp/solver_interface.h +++ b/src/search/lp/solver_interface.h @@ -12,6 +12,7 @@ class NamedVector; namespace lp { class LinearProgram; class LPConstraint; +enum class LPConstraintSense; class SolverInterface { public: @@ -26,8 +27,8 @@ class SolverInterface { virtual void set_objective_coefficients( const std::vector &coefficients) = 0; virtual void set_objective_coefficient(int index, double coefficient) = 0; - virtual void set_constraint_lower_bound(int index, double bound) = 0; - virtual void set_constraint_upper_bound(int index, double bound) = 0; + virtual void set_constraint_rhs(int index, double right_hand_side) = 0; + virtual void set_constraint_sense(int index, LPConstraintSense sense) = 0; virtual void set_variable_lower_bound(int index, double bound) = 0; virtual void set_variable_upper_bound(int index, double bound) = 0; diff --git a/src/search/lp/soplex_solver_interface.cc b/src/search/lp/soplex_solver_interface.cc index c38f776368..e420372666 100644 --- a/src/search/lp/soplex_solver_interface.cc +++ b/src/search/lp/soplex_solver_interface.cc @@ -4,6 +4,8 @@ #include "../utils/system.h" +#include + using namespace std; using namespace soplex; @@ -26,16 +28,35 @@ static LPRowSetReal constraints_to_row_set( LPRowSetReal rows(num_rows, num_nonzeros); for (const LPConstraint &constraint : constraints) { - const vector variables = constraint.get_variables(); - const vector coefficients = constraint.get_coefficients(); + const vector &variables = constraint.get_variables(); + const vector &coefficients = constraint.get_coefficients(); int num_entries = coefficients.size(); soplex::DSVectorReal entries(num_entries); for (int i = 0; i < num_entries; ++i) { entries.add(variables[i], coefficients[i]); } - rows.add( - constraint.get_lower_bound(), entries, - constraint.get_upper_bound()); + double b = constraint.get_right_hand_side(); + LPConstraintSense sense = constraint.get_sense(); + double lhs, rhs; + switch (sense) { + case LPConstraintSense::LESS_EQUAL: + lhs = -soplex::infinity; + rhs = b; + break; + case LPConstraintSense::GREATER_EQUAL: + lhs = b; + rhs = soplex::infinity; + break; + case LPConstraintSense::EQUAL: + lhs = b; + rhs = b; + break; + default: + ABORT( + "Invalid constraint sense code: " + + to_string(static_cast(sense))); + } + rows.add(lhs, entries, rhs); } return rows; } @@ -71,12 +92,21 @@ void SoPlexSolverInterface::load_problem(const LinearProgram &lp) { soplex.addRowsReal(constraints_to_row_set(lp.get_constraints())); num_permanent_constraints = lp.get_constraints().size(); num_temporary_constraints = 0; + constraint_senses.clear(); + for (const LPConstraint &constraint : lp.get_constraints()) { + constraint_senses.push_back(constraint.get_sense()); + } } void SoPlexSolverInterface::add_temporary_constraints( const named_vector::NamedVector &constraints) { soplex.addRowsReal(constraints_to_row_set(constraints)); - num_temporary_constraints = constraints.size(); + int num_constraints = constraints.size(); + num_temporary_constraints += num_constraints; + for (int i = 0; i < num_constraints; ++i) { + const LPConstraint &c = constraints[i]; + constraint_senses.push_back(c.get_sense()); + } } void SoPlexSolverInterface::clear_temporary_constraints() { @@ -84,6 +114,7 @@ void SoPlexSolverInterface::clear_temporary_constraints() { int first = num_permanent_constraints; int last = first + num_temporary_constraints - 1; soplex.removeRowRangeReal(first, last, nullptr); + constraint_senses.resize(num_permanent_constraints); num_temporary_constraints = 0; } } @@ -105,16 +136,6 @@ void SoPlexSolverInterface::set_objective_coefficient( soplex.changeObjReal(index, coefficient); } -void SoPlexSolverInterface::set_constraint_lower_bound( - int index, double bound) { - soplex.changeLhsReal(index, bound); -} - -void SoPlexSolverInterface::set_constraint_upper_bound( - int index, double bound) { - soplex.changeRhsReal(index, bound); -} - void SoPlexSolverInterface::set_variable_lower_bound(int index, double bound) { soplex.changeLowerReal(index, bound); } @@ -240,4 +261,70 @@ bool SoPlexSolverInterface::has_temporary_constraints() const { void SoPlexSolverInterface::print_statistics() const { soplex.printStatistics(cout); } + +void SoPlexSolverInterface::set_constraint_rhs(int index, double b) { + const LPConstraintSense sense = constraint_senses[index]; + + switch (sense) { + case LPConstraintSense::GREATER_EQUAL: + soplex.changeLhsReal(index, b); + break; + + case LPConstraintSense::LESS_EQUAL: + soplex.changeRhsReal(index, b); + break; + + case LPConstraintSense::EQUAL: + soplex.changeLhsReal(index, b); + soplex.changeRhsReal(index, b); + break; + + default: + ABORT( + "Invalid constraint sense code: " + + to_string(static_cast(sense))); + } +} + +void SoPlexSolverInterface::set_constraint_sense( + int index, LPConstraintSense sense) { + double lhs = soplex.lhsReal(index); + double rhs = soplex.rhsReal(index); + + bool lhs_is_finite = (-infinity < lhs); + bool rhs_is_finite = (rhs < infinity); + + double b; + if (!lhs_is_finite && rhs_is_finite) { + b = rhs; + } else if (lhs_is_finite && !rhs_is_finite) { + b = lhs; + } else if (lhs_is_finite && rhs_is_finite && lhs == rhs) { + b = rhs; + } else { + cerr << "Invalid constraint." << endl; + utils::exit_with(utils::ExitCode::SEARCH_CRITICAL_ERROR); + } + + switch (sense) { + case LPConstraintSense::LESS_EQUAL: + soplex.changeLhsReal(index, -infinity); + soplex.changeRhsReal(index, b); + break; + case LPConstraintSense::GREATER_EQUAL: + soplex.changeLhsReal(index, b); + soplex.changeRhsReal(index, infinity); + break; + case LPConstraintSense::EQUAL: + soplex.changeLhsReal(index, b); + soplex.changeRhsReal(index, b); + break; + default: + ABORT( + "Invalid constraint sense code: " + + to_string(static_cast(sense))); + } + + constraint_senses[index] = sense; +} } diff --git a/src/search/lp/soplex_solver_interface.h b/src/search/lp/soplex_solver_interface.h index 6960f264e1..fc3c780a1e 100644 --- a/src/search/lp/soplex_solver_interface.h +++ b/src/search/lp/soplex_solver_interface.h @@ -27,6 +27,7 @@ class SoPlexSolverInterface : public SolverInterface { mutable soplex::SoPlex soplex; int num_permanent_constraints; int num_temporary_constraints; + std::vector constraint_senses; public: SoPlexSolverInterface(); @@ -40,8 +41,9 @@ class SoPlexSolverInterface : public SolverInterface { const std::vector &coefficients) override; virtual void set_objective_coefficient( int index, double coefficient) override; - virtual void set_constraint_lower_bound(int index, double bound) override; - virtual void set_constraint_upper_bound(int index, double bound) override; + virtual void set_constraint_rhs(int index, double right_hand_side) override; + virtual void set_constraint_sense( + int index, LPConstraintSense sense) override; virtual void set_variable_lower_bound(int index, double bound) override; virtual void set_variable_upper_bound(int index, double bound) override; diff --git a/src/search/operator_counting/delete_relaxation_if_constraints.cc b/src/search/operator_counting/delete_relaxation_if_constraints.cc index e52818edb4..1871d64daa 100644 --- a/src/search/operator_counting/delete_relaxation_if_constraints.cc +++ b/src/search/operator_counting/delete_relaxation_if_constraints.cc @@ -102,7 +102,6 @@ void DeleteRelaxationIFConstraints::create_constraints( const TaskProxy &task_proxy, lp::LinearProgram &lp) { LPVariables &variables = lp.get_variables(); LPConstraints &constraints = lp.get_constraints(); - double infinity = lp.get_infinity(); OperatorsProxy ops = task_proxy.get_operators(); VariablesProxy vars = task_proxy.get_variables(); @@ -126,7 +125,8 @@ void DeleteRelaxationIFConstraints::create_constraints( constraint_ids[var_id].resize(var.get_domain_size()); for (int value = 0; value < var.get_domain_size(); ++value) { constraint_ids[var_id][value] = constraints.size(); - constraints.emplace_back(0, infinity); + constraints.emplace_back(lp::LPConstraintSense::GREATER_EQUAL, 0); + /* We add "- R_f" here, collect the achiever below and adapt the lower bound in each iteration, i.e., in update_constraints. */ @@ -149,7 +149,8 @@ void DeleteRelaxationIFConstraints::create_constraints( for (OperatorProxy op : ops) { for (EffectProxy eff : op.get_effects()) { FactPair f = eff.get_fact().get_pair(); - lp::LPConstraint constraint(0, infinity); + lp::LPConstraint constraint( + lp::LPConstraintSense::GREATER_EQUAL, 0); constraint.insert(get_var_op_used(op), 1); constraint.insert(get_var_first_achiever(op, f), -1); constraints.push_back(constraint); @@ -162,7 +163,8 @@ void DeleteRelaxationIFConstraints::create_constraints( */ for (OperatorProxy op : ops) { for (FactProxy f : op.get_preconditions()) { - lp::LPConstraint constraint(0, infinity); + lp::LPConstraint constraint( + lp::LPConstraintSense::GREATER_EQUAL, 0); constraint.insert(get_var_fact_reached(f.get_pair()), 1); constraint.insert(get_var_op_used(op), -1); constraints.push_back(constraint); @@ -176,7 +178,8 @@ void DeleteRelaxationIFConstraints::create_constraints( */ for (OperatorProxy op : ops) { for (FactProxy f : op.get_preconditions()) { - lp::LPConstraint constraint(0, infinity); + lp::LPConstraint constraint( + lp::LPConstraintSense::GREATER_EQUAL, 0); constraint.insert(get_var_op_time(op), 1); constraint.insert(get_var_fact_time(f.get_pair()), -1); constraints.push_back(constraint); @@ -195,7 +198,8 @@ void DeleteRelaxationIFConstraints::create_constraints( for (OperatorProxy op : ops) { for (EffectProxy eff : op.get_effects()) { FactPair f = eff.get_fact().get_pair(); - lp::LPConstraint constraint(1 - M, infinity); + lp::LPConstraint constraint( + lp::LPConstraintSense::GREATER_EQUAL, 1 - M); constraint.insert(get_var_fact_time(f), 1); constraint.insert(get_var_op_time(op), -1); constraint.insert(get_var_first_achiever(op, f), -M); @@ -209,7 +213,7 @@ void DeleteRelaxationIFConstraints::create_constraints( U_o <= C_o for each operator o. */ for (OperatorProxy op : ops) { - lp::LPConstraint constraint(0, infinity); + lp::LPConstraint constraint(lp::LPConstraintSense::GREATER_EQUAL, 0); constraint.insert(op.get_id(), 1); constraint.insert(get_var_op_used(op), -1); constraints.push_back(constraint); @@ -227,13 +231,12 @@ bool DeleteRelaxationIFConstraints::update_constraints( const State &state, lp::LPSolver &lp_solver) { // Unset old bounds. for (FactPair f : last_state) { - lp_solver.set_constraint_lower_bound(get_constraint_id(f), 0); + lp_solver.set_constraint_rhs(get_constraint_id(f), 0); } last_state.clear(); // Set new bounds. for (FactProxy f : state) { - lp_solver.set_constraint_lower_bound( - get_constraint_id(f.get_pair()), -1); + lp_solver.set_constraint_rhs(get_constraint_id(f.get_pair()), -1); last_state.push_back(f.get_pair()); } return false; diff --git a/src/search/operator_counting/delete_relaxation_rr_constraints.cc b/src/search/operator_counting/delete_relaxation_rr_constraints.cc index 65fe249ebd..354d9dd2f9 100644 --- a/src/search/operator_counting/delete_relaxation_rr_constraints.cc +++ b/src/search/operator_counting/delete_relaxation_rr_constraints.cc @@ -286,7 +286,6 @@ void DeleteRelaxationRRConstraints::create_constraints( lp::LinearProgram &lp) { LPVariables &variables = lp.get_variables(); LPConstraints &constraints = lp.get_constraints(); - double infinity = lp.get_infinity(); OperatorsProxy ops = task_proxy.get_operators(); VariablesProxy vars = task_proxy.get_variables(); @@ -309,7 +308,7 @@ void DeleteRelaxationRRConstraints::create_constraints( constraint_offsets.push_back(constraints.size()); for (int value_p = 0; value_p < var_p.get_domain_size(); ++value_p) { FactPair fact_p(var_id_p, value_p); - lp::LPConstraint constraint(0, 0); + lp::LPConstraint constraint(lp::LPConstraintSense::EQUAL, 0); constraint.insert(lp_var_ids.id_of_fp(fact_p), 1); constraints.push_back(move(constraint)); } @@ -348,7 +347,8 @@ void DeleteRelaxationRRConstraints::create_constraints( pair key = make_pair(pre, eff); if (!constraint3_ids.contains(key)) { constraint3_ids[key] = constraints.size(); - lp::LPConstraint constraint(0, 1); + lp::LPConstraint constraint( + lp::LPConstraintSense::GREATER_EQUAL, 0); constraint.insert(lp_var_ids.id_of_fp(pre), 1); constraints.push_back(move(constraint)); } @@ -388,7 +388,8 @@ void DeleteRelaxationRRConstraints::create_constraints( for (OperatorProxy op : ops) { for (EffectProxy eff_proxy : op.get_effects()) { FactPair eff = eff_proxy.get_fact().get_pair(); - lp::LPConstraint constraint(0, infinity); + lp::LPConstraint constraint( + lp::LPConstraintSense::GREATER_EQUAL, 0); constraint.insert(lp_var_ids.id_of_fpa(eff, op), -1); constraint.insert(op.get_id(), 1); constraints.push_back(move(constraint)); @@ -401,7 +402,6 @@ void DeleteRelaxationRRConstraints::create_constraints_ve( const DeleteRelaxationRRConstraints::LPVariableIDs &lp_var_ids, lp::LinearProgram &lp) { LPConstraints &constraints = lp.get_constraints(); - double infinity = lp.get_infinity(); OperatorsProxy ops = task_proxy.get_operators(); /* @@ -417,7 +417,8 @@ void DeleteRelaxationRRConstraints::create_constraints_ve( FactPair pre = pre_proxy.get_pair(); for (EffectProxy eff_proxy : op.get_effects()) { FactPair eff = eff_proxy.get_fact().get_pair(); - lp::LPConstraint constraint(0, infinity); + lp::LPConstraint constraint( + lp::LPConstraintSense::GREATER_EQUAL, 0); constraint.insert(lp_var_ids.id_of_e(make_pair(pre, eff)), 1); constraint.insert(lp_var_ids.id_of_fpa(eff, op), -1); constraints.push_back(move(constraint)); @@ -439,7 +440,7 @@ void DeleteRelaxationRRConstraints::create_constraints_ve( pair reverse_edge = make_pair(edge.second, edge.first); if (lp_var_ids.has_e(reverse_edge)) { - lp::LPConstraint constraint(-infinity, 1); + lp::LPConstraint constraint(lp::LPConstraintSense::LESS_EQUAL, 1); constraint.insert(lp_var_ids.id_of_e(edge), 1); constraint.insert(lp_var_ids.id_of_e(reverse_edge), 1); constraints.push_back(move(constraint)); @@ -457,7 +458,7 @@ void DeleteRelaxationRRConstraints::create_constraints_ve( not have both p_i ordered before p_j, and p_j ordered before p_k. */ for (auto [pi, pj, pk] : ve_graph.get_delta()) { - lp::LPConstraint constraint(-infinity, 1); + lp::LPConstraint constraint(lp::LPConstraintSense::LESS_EQUAL, 1); constraint.insert(lp_var_ids.id_of_e(make_pair(pi, pj)), 1); constraint.insert(lp_var_ids.id_of_e(make_pair(pj, pk)), 1); constraint.insert(lp_var_ids.id_of_e(make_pair(pi, pk)), -1); @@ -481,7 +482,6 @@ void DeleteRelaxationRRConstraints::create_constraints_tl( preconditions, we have to achieve p_i before p_j. */ LPConstraints &constraints = lp.get_constraints(); - double infinity = lp.get_infinity(); int num_facts = 0; for (VariableProxy var : task_proxy.get_variables()) { num_facts += var.get_domain_size(); @@ -496,7 +496,8 @@ void DeleteRelaxationRRConstraints::create_constraints_tl( // Prevail conditions are compiled away in the paper. continue; } - lp::LPConstraint constraint(-infinity, num_facts - 1); + lp::LPConstraint constraint( + lp::LPConstraintSense::LESS_EQUAL, num_facts - 1); constraint.insert(lp_var_ids.id_of_t(pre), 1); constraint.insert(lp_var_ids.id_of_t(eff), -1); constraint.insert(lp_var_ids.id_of_fpa(eff, op), num_facts); @@ -541,15 +542,13 @@ bool DeleteRelaxationRRConstraints::update_constraints( int con_id; for (FactPair f : last_state) { con_id = get_constraint_id(f); - lp_solver.set_constraint_lower_bound(con_id, 0); - lp_solver.set_constraint_upper_bound(con_id, 0); + lp_solver.set_constraint_rhs(con_id, 0); } last_state.clear(); // Set new bounds. for (FactProxy f : state) { con_id = get_constraint_id(f.get_pair()); - lp_solver.set_constraint_lower_bound(con_id, 1); - lp_solver.set_constraint_upper_bound(con_id, 1); + lp_solver.set_constraint_rhs(con_id, 1); last_state.push_back(f.get_pair()); } return false; diff --git a/src/search/operator_counting/lm_cut_constraints.cc b/src/search/operator_counting/lm_cut_constraints.cc index 0f0a9ad9cb..a0c7f993d3 100644 --- a/src/search/operator_counting/lm_cut_constraints.cc +++ b/src/search/operator_counting/lm_cut_constraints.cc @@ -22,11 +22,10 @@ bool LMCutConstraints::update_constraints( const State &state, lp::LPSolver &lp_solver) { assert(landmark_generator); named_vector::NamedVector constraints; - double infinity = lp_solver.get_infinity(); bool dead_end = landmark_generator->compute_landmarks( state, nullptr, [&](const vector &op_ids, int /*cost*/) { - constraints.emplace_back(1.0, infinity); + constraints.emplace_back(lp::LPConstraintSense::GREATER_EQUAL, 1.0); lp::LPConstraint &landmark_constraint = constraints.back(); for (int op_id : op_ids) { landmark_constraint.insert(op_id, 1.0); diff --git a/src/search/operator_counting/pho_constraints.cc b/src/search/operator_counting/pho_constraints.cc index 3e874b4b76..18f50d60c0 100644 --- a/src/search/operator_counting/pho_constraints.cc +++ b/src/search/operator_counting/pho_constraints.cc @@ -38,7 +38,7 @@ void PhOConstraints::initialize_constraints( lp.get_constraints(); constraint_offset = constraints.size(); for (const shared_ptr &pdb : *pdbs) { - constraints.emplace_back(0, lp.get_infinity()); + constraints.emplace_back(lp::LPConstraintSense::GREATER_EQUAL, 0); lp::LPConstraint &constraint = constraints.back(); for (OperatorProxy op : task_proxy.get_operators()) { if (pdbs::is_operator_relevant(pdb->get_pattern(), op)) { @@ -58,7 +58,7 @@ bool PhOConstraints::update_constraints( if (h == numeric_limits::max()) { return true; } - lp_solver.set_constraint_lower_bound(constraint_id, h); + lp_solver.set_constraint_rhs(constraint_id, h); } return false; } diff --git a/src/search/operator_counting/state_equation_constraints.cc b/src/search/operator_counting/state_equation_constraints.cc index 81112fcfbf..0aa933d6bf 100644 --- a/src/search/operator_counting/state_equation_constraints.cc +++ b/src/search/operator_counting/state_equation_constraints.cc @@ -58,7 +58,8 @@ void StateEquationConstraints::add_constraints( named_vector::NamedVector &constraints, double infinity) { for (vector &var_propositions : propositions) { for (Proposition &prop : var_propositions) { - lp::LPConstraint constraint(-infinity, infinity); + lp::LPConstraint constraint( + lp::LPConstraintSense::GREATER_EQUAL, -infinity); add_indices_to_constraint(constraint, prop.always_produced_by, 1.0); add_indices_to_constraint( constraint, prop.sometimes_produced_by, 1.0); @@ -111,7 +112,7 @@ bool StateEquationConstraints::update_constraints( if (goal_state[var] == value) { ++lower_bound; } - lp_solver.set_constraint_lower_bound( + lp_solver.set_constraint_rhs( prop.constraint_index, lower_bound); } } diff --git a/src/search/potentials/potential_optimizer.cc b/src/search/potentials/potential_optimizer.cc index 6a1c5955cd..f589ca1458 100644 --- a/src/search/potentials/potential_optimizer.cc +++ b/src/search/potentials/potential_optimizer.cc @@ -118,7 +118,8 @@ void PotentialOptimizer::construct_lp() { for (FactProxy pre : op.get_preconditions()) { var_to_precondition[pre.get_variable().get_id()] = pre.get_value(); } - lp::LPConstraint constraint(-infinity, op.get_cost()); + lp::LPConstraint constraint( + lp::LPConstraintSense::LESS_EQUAL, op.get_cost()); vector> coefficients; for (EffectProxy effect : op.get_effects()) { VariableProxy var = effect.get_fact().get_variable(); @@ -185,7 +186,7 @@ void PotentialOptimizer::construct_lp() { // Create constraint: P_{V=v} <= P_{V=u} // Note that we could eliminate variables P_{V=u} if V is // undefined in the goal. - lp::LPConstraint constraint(-infinity, 0); + lp::LPConstraint constraint(lp::LPConstraintSense::LESS_EQUAL, 0); constraint.insert(val_lp, 1); constraint.insert(undef_val_lp, -1); lp_constraints.push_back(constraint);