24#include "absl/log/check.h"
25#include "absl/memory/memory.h"
26#include "absl/status/status.h"
27#include "absl/status/statusor.h"
28#include "absl/strings/str_cat.h"
29#include "absl/strings/str_join.h"
30#include "absl/time/clock.h"
31#include "absl/time/time.h"
32#include "absl/types/span.h"
51 std::vector<std::string> warnings;
52 if (parameters.has_threads() && parameters.threads() > 1) {
53 warnings.push_back(absl::StrCat(
54 "XpressSolver only supports parameters.threads = 1; value ",
55 parameters.threads(),
" is not supported"));
61 warnings.emplace_back(absl::StrCat(
62 "XpressSolver does not support the 'lp_algorithm' parameter value: ",
65 if (parameters.has_objective_limit()) {
66 warnings.emplace_back(
"XpressSolver does not support objective_limit yet");
68 if (parameters.has_best_bound_limit()) {
69 warnings.emplace_back(
"XpressSolver does not support best_bound_limit yet");
71 if (parameters.has_cutoff_limit()) {
72 warnings.emplace_back(
"XpressSolver does not support cutoff_limit yet");
74 if (!warnings.empty()) {
75 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
77 return absl::OkStatus();
94 return absl::InvalidArgumentError(
"Xpress is not correctly installed.");
103 auto xpress_solver = absl::WrapUnique(
new XpressSolver(std::move(xpr)));
105 return xpress_solver;
108absl::Status XpressSolver::LoadModel(
const ModelProto& input_model) {
109 CHECK(xpress_ !=
nullptr);
115 return absl::OkStatus();
117absl::Status XpressSolver::AddNewVariables(
118 const VariablesProto& new_variables) {
119 const int num_new_variables = new_variables.lower_bounds().size();
120 std::vector<char> variable_type(num_new_variables);
121 int n_variables = xpress_->GetNumberOfVariables();
122 for (
int j = 0; j < num_new_variables; ++j) {
123 const VarId
id = new_variables.ids(j);
127 if (new_variables.integers(j)) {
129 return absl::UnimplementedError(
"XpressSolver does not handle MIPs yet");
133 new_variables.upper_bounds(),
139 return absl::OkStatus();
142XpressSolver::XpressSolver(std::unique_ptr<Xpress> g_xpress)
143 : xpress_(std::move(g_xpress)) {}
145absl::Status XpressSolver::AddNewLinearConstraints(
148 const int num_new_constraints = constraints.lower_bounds().size();
149 std::vector<char> constraint_sense;
150 constraint_sense.reserve(num_new_constraints);
151 std::vector<double> constraint_rhs;
152 constraint_rhs.reserve(num_new_constraints);
153 std::vector<double> constraint_rng;
154 constraint_rng.reserve(num_new_constraints);
155 int n_constraints = xpress_->GetNumberOfConstraints();
156 for (
int i = 0;
i < num_new_constraints; ++
i) {
157 const int64_t
id = constraints.ids(i);
158 LinearConstraintData& constraint_data =
160 const double lb = constraints.lower_bounds(i);
161 const double ub = constraints.upper_bounds(i);
162 constraint_data.lower_bound = lb;
163 constraint_data.upper_bound = ub;
164 constraint_data.constraint_index =
i + n_constraints;
168 const bool lb_is_xprs_neg_inf = lb <= kMinusInf;
169 const bool ub_is_xprs_pos_inf = ub >= kPlusInf;
170 if (lb_is_xprs_neg_inf && !ub_is_xprs_pos_inf) {
173 }
else if (!lb_is_xprs_neg_inf && ub_is_xprs_pos_inf) {
176 }
else if (lb == ub) {
184 constraint_sense.emplace_back(sense);
185 constraint_rhs.emplace_back(rhs);
186 constraint_rng.emplace_back(rng);
189 return xpress_->AddConstrs(constraint_sense, constraint_rhs, constraint_rng);
192absl::Status XpressSolver::AddSingleObjective(
const ObjectiveProto& objective) {
195 is_maximize_ = objective.maximize();
197 std::vector<int> index;
198 index.reserve(objective.linear_coefficients().ids_size());
199 for (
const int64_t
id : objective.linear_coefficients().ids()) {
200 index.push_back(variables_map_.at(
id));
203 objective.offset(), index, objective.linear_coefficients().values()));
205 const int num_terms = objective.quadratic_coefficients().row_ids().size();
207 std::vector<int> first_var_index(num_terms);
208 std::vector<int> second_var_index(num_terms);
209 std::vector<double> coefficients(num_terms);
210 for (
int k = 0; k < num_terms; ++k) {
211 const int64_t row_id = objective.quadratic_coefficients().row_ids(k);
212 const int64_t column_id =
213 objective.quadratic_coefficients().column_ids(k);
214 first_var_index[k] = variables_map_.at(row_id);
215 second_var_index[k] = variables_map_.at(column_id);
218 double m = first_var_index[k] == second_var_index[k] ? 2 : 1;
219 coefficients[k] = objective.quadratic_coefficients().coefficients(k) * m;
222 first_var_index, second_var_index, coefficients));
224 return absl::OkStatus();
227absl::Status XpressSolver::ChangeCoefficients(
229 const int num_coefficients = matrix.row_ids().size();
230 std::vector<int> row_index;
231 row_index.reserve(num_coefficients);
232 std::vector<int> col_index;
233 col_index.reserve(num_coefficients);
234 for (
int k = 0; k < num_coefficients; ++k) {
236 linear_constraints_map_.at(matrix.row_ids(k)).constraint_index);
237 col_index.push_back(variables_map_.at(matrix.column_ids(k)));
239 return xpress_->ChgCoeffs(row_index, col_index, matrix.coefficients());
249 const absl::Time start = absl::Now();
260 ListInvertedBounds());
269 RETURN_IF_ERROR(CallXpressSolve(parameters)) <<
"Error during XPRESS solve";
273 ExtractSolveResultProto(start, model_parameters, parameters));
278std::string XpressSolver::GetLpOptimizationFlags(
294 switch (default_alg.value_or(-1)) {
310absl::Status XpressSolver::CallXpressSolve(
313 if (parameters.enable_output()) {
315 <<
"Unable to enable XPRESS logs";
323 <<
"Could not set iteration limits.";
324 RETURN_IF_ERROR(xpress_->LpOptimize(GetLpOptimizationFlags(parameters)));
327 xpress_lp_status_ = {primal_status, dual_status};
332 RETURN_IF_ERROR(xpress_->PostSolve()) <<
"Post-solve failed in XPRESS";
335 if (parameters.enable_output()) {
337 <<
"Unable to disable XPRESS logs";
339 return absl::OkStatus();
342absl::Status XpressSolver::SetLpIterLimits(
347 if (parameters.has_iteration_limit()) {
350 <<
"Could not set XPRS_LPITERLIMIT";
353 <<
"Could not set XPRS_BARITERLIMIT";
356 <<
"Could not reset XPRS_LPITERLIMIT to its default value";
358 <<
"Could not reset XPRS_BARITERLIMIT to its default value";
360 return absl::OkStatus();
363absl::StatusOr<SolveResultProto> XpressSolver::ExtractSolveResultProto(
366 SolveResultProto result;
368 GetSolution(model_parameters, solve_parameters));
369 *result.add_solutions() = std::move(
solution);
374 *result.mutable_termination(),
375 ConvertTerminationReason(best_primal_bound, best_dual_bound));
379absl::StatusOr<double> XpressSolver::GetBestPrimalBound()
const {
381 (isPrimalFeasible() ||
387 return is_maximize_ ? kMinusInf : kPlusInf;
390absl::StatusOr<double> XpressSolver::GetBestDualBound()
const {
392 (isPrimalFeasible() ||
398 return is_maximize_ ? kPlusInf : kMinusInf;
401absl::StatusOr<SolutionProto> XpressSolver::GetSolution(
405 return absl::UnimplementedError(
"XpressSolver does not handle MIPs yet");
407 return GetLpSolution(model_parameters, solve_parameters);
411absl::StatusOr<SolutionProto> XpressSolver::GetLpSolution(
415 int nVars = xpress_->GetNumberOfVariables();
416 int nCons = xpress_->GetNumberOfConstraints();
417 std::vector<double> primals(nVars);
418 std::vector<double> duals(nCons);
419 std::vector<double> reducedCosts(nVars);
423 ->GetLpSol(absl::MakeSpan(primals), absl::MakeSpan(duals),
424 absl::MakeSpan(reducedCosts))
429 if (isPrimalFeasible()) {
431 solution.mutable_primal_solution()->set_feasibility_status(
432 getLpSolutionStatus());
434 solution.mutable_primal_solution()->set_objective_value(primalBound);
435 XpressVectorToSparseDoubleVector(
436 primals, variables_map_,
437 *
solution.mutable_primal_solution()->mutable_variable_values(),
438 model_parameters.variable_values_filter());
443 solution.mutable_dual_solution()->set_feasibility_status(
444 getDualSolutionStatus());
446 solution.mutable_dual_solution()->set_objective_value(dualBound);
447 XpressVectorToSparseDoubleVector(
448 duals, linear_constraints_map_,
449 *
solution.mutable_dual_solution()->mutable_dual_values(),
450 model_parameters.dual_values_filter());
451 XpressVectorToSparseDoubleVector(
452 reducedCosts, variables_map_,
453 *
solution.mutable_dual_solution()->mutable_reduced_costs(),
454 model_parameters.reduced_costs_filter());
459 if (basis.has_value()) {
460 *
solution.mutable_basis() = std::move(*basis);
465bool XpressSolver::isPrimalFeasible()
const {
475bool XpressSolver::isDualFeasible()
const {
477 return isPrimalFeasible();
488 switch (xpress_lp_status_.primal_status) {
510 if (isDualFeasible()) {
513 switch (xpress_lp_status_.dual_status) {
570absl::Status XpressSolver::SetXpressStartingBasis(
const BasisProto& basis) {
571 std::vector<int> xpress_var_basis_status(xpress_->GetNumberOfVariables());
572 for (
const auto [
id, value] :
MakeView(basis.variable_status())) {
573 xpress_var_basis_status[variables_map_.
at(
id)] =
576 std::vector<int> xpress_constr_basis_status(
577 xpress_->GetNumberOfConstraints());
578 for (
const auto [
id, value] :
MakeView(basis.constraint_status())) {
579 xpress_constr_basis_status[linear_constraints_map_.
at(
id)
583 return xpress_->SetStartingBasis(xpress_constr_basis_status,
584 xpress_var_basis_status);
587absl::StatusOr<std::optional<BasisProto>> XpressSolver::GetBasisIfAvailable(
589 std::vector<int> xprs_variable_basis_status;
590 std::vector<int> xprs_constraint_basis_status;
592 ->GetBasis(xprs_constraint_basis_status, xprs_variable_basis_status)
599 for (
auto [variable_id, xprs_variable_index] : variables_map_) {
600 basis.mutable_variable_status()->add_ids(variable_id);
602 xprs_variable_basis_status[xprs_variable_index],
false);
604 return absl::InternalError(
605 absl::StrCat(
"Invalid Xpress variable basis status: ",
606 xprs_variable_basis_status[xprs_variable_index]));
608 basis.mutable_variable_status()->add_values(variable_status);
612 for (
auto [constraint_id, xprs_ct_index] : linear_constraints_map_) {
613 basis.mutable_constraint_status()->add_ids(constraint_id);
615 xprs_constraint_basis_status[xprs_ct_index.constraint_index],
true);
617 return absl::InternalError(absl::StrCat(
618 "Invalid Xpress constraint basis status: ",
619 xprs_constraint_basis_status[xprs_ct_index.constraint_index]));
621 basis.mutable_constraint_status()->add_values(status);
625 basis.set_basic_dual_feasibility(
630absl::StatusOr<SolveStatsProto> XpressSolver::GetSolveStats(
631 absl::Time start)
const {
632 SolveStatsProto solve_stats;
634 solve_stats.mutable_solve_time()));
639 solve_stats.set_simplex_iterations(iters);
644 solve_stats.set_barrier_iterations(iters);
652void XpressSolver::XpressVectorToSparseDoubleVector(
653 absl::Span<const double> xpress_values,
const T& map,
656 SparseVectorFilterPredicate predicate(filter);
657 for (
auto [
id, xpress_data] : map) {
658 const double value = xpress_values[get_model_index(xpress_data)];
659 if (predicate.AcceptsAndUpdate(
id, value)) {
661 result.add_values(value);
666absl::StatusOr<TerminationProto> XpressSolver::ConvertTerminationReason(
667 double best_primal_bound,
double best_dual_bound)
const {
669 switch (xpress_lp_status_.primal_status) {
673 "Problem solve has not started (XPRS_LP_UNSTARTED)");
682 is_maximize_,
"Objective worse than cutoff (XPRS_LP_CUTOFF)");
688 "Solve did not finish (XPRS_LP_UNFINISHED)");
691 "Xpress status XPRS_LP_UNBOUNDED");
694 is_maximize_,
"Cutoff in dual (XPRS_LP_CUTOFF_IN_DUAL)");
698 "Problem could not be solved due to numerical issues "
699 "(XPRS_LP_UNSOLVED)");
702 "Problem contains quadratic data, which is "
703 "not convex (XPRS_LP_NONCONVEX)");
705 return absl::InternalError(
706 absl::StrCat(
"Missing Xpress LP status code case: ",
707 xpress_lp_status_.primal_status));
710 return absl::UnimplementedError(
"XpressSolver does not handle MIPs yet");
719absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
723 return absl::UnimplementedError(
724 "XPRESS does not provide a method to compute an infeasible subsystem");
727absl::StatusOr<InvertedBounds> XpressSolver::ListInvertedBounds()
const {
732 for (
const auto& [
id, index] : variables_map_) {
733 if (var_lbs[index] > var_ubs[index]) {
741 for (
const auto& [
id, cstr_data] : linear_constraints_map_) {
742 if (cstr_data.lower_bound > cstr_data.upper_bound) {
750 return inverted_bounds;
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
mapped_type & at(const key_arg< K > &key)
const ::operations_research::math_opt::VariablesProto & variables() const
const ::operations_research::math_opt::LinearConstraintsProto & linear_constraints() const
const ::std::string & name() const
const ::operations_research::math_opt::ObjectiveProto & objective() const
const ::operations_research::math_opt::SparseDoubleMatrixProto & linear_constraint_matrix() const
const ::operations_research::math_opt::BasisProto & initial_basis() const
bool has_initial_basis() const
.operations_research.math_opt.BasisProto initial_basis = 4;
::operations_research::math_opt::LPAlgorithmProto lp_algorithm() const
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto ¶meters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *interrupter) override
Solves the optimization problem.
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
Updates the problem (not implemented yet)
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto ¶meters, MessageCallback message_cb, const SolveInterrupter *interrupter) override
Computes the infeasible subsystem (not implemented yet)
static absl::StatusOr< std::unique_ptr< XpressSolver > > New(const ModelProto &input_model, const SolverInterface::InitArgs &init_args)
Creates the XPRESS solver and loads the model into it.
static absl::StatusOr< std::unique_ptr< Xpress > > New(absl::string_view model_name)
Creates a new Xpress.
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
auto & InsertKeyOrDie(Collection *const collection, const typename Collection::value_type::first_type &key)
An object oriented wrapper for quadratic constraints in ModelStorage.
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
@ TERMINATION_REASON_OTHER_ERROR
@ TERMINATION_REASON_NUMERICAL_ERROR
constexpr SupportedProblemStructures kXpressSupportedStructures
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
@ SOLUTION_STATUS_UNDETERMINED
@ SOLUTION_STATUS_INFEASIBLE
@ SOLUTION_STATUS_UNSPECIFIED
@ SOLUTION_STATUS_FEASIBLE
@ LP_ALGORITHM_DUAL_SIMPLEX
@ LP_ALGORITHM_UNSPECIFIED
@ LP_ALGORITHM_PRIMAL_SIMPLEX
BasisStatusProto XpressToMathOptBasisStatus(const int status, bool isConstraint)
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
@ FEASIBILITY_STATUS_FEASIBLE
@ FEASIBILITY_STATUS_UNDETERMINED
TerminationProto FeasibleTerminationProto(const bool is_maximize, const LimitProto limit, const double primal_objective, const std::optional< double > optional_dual_objective, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
TerminationProto CutoffTerminationProto(bool is_maximize, const absl::string_view detail)
Calls NoSolutionFoundTerminationProto() with LIMIT_CUTOFF LIMIT.
@ BASIS_STATUS_AT_UPPER_BOUND
@ BASIS_STATUS_UNSPECIFIED
@ BASIS_STATUS_AT_LOWER_BOUND
TerminationProto InfeasibleTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
TerminationProto UnboundedTerminationProto(const bool is_maximize, const absl::string_view detail)
int MathOptToXpressBasisStatus(const BasisStatusProto status, bool isConstraint)
In SWIG mode, we don't want anything besides these top-level includes.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
std::string ProtoEnumToString(ProtoEnumType enum_value)
bool XpressIsCorrectlyInstalled()
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
std::vector< int64_t > linear_constraints
Ids of the linear constraints with inverted bounds.
std::vector< int64_t > variables
Ids of the variables with inverted bounds.
absl::Status ToStatus() const
Initialization arguments.
#define XPRS_LP_CUTOFF_IN_DUAL
#define XPRS_MIP_SOLUTION
#define XPRS_LP_NONCONVEX
#define XPRS_GREATER_EQUAL
#define XPRS_SOLSTATUS_FEASIBLE
#define XPRS_LP_UNFINISHED
#define XPRS_LP_UNSTARTED
#define XPRS_SOLSTATUS_UNBOUNDED
#define XPRS_SOLSTATUS_INFEASIBLE
#define XPRS_BARITERLIMIT
#define XPRS_LP_UNBOUNDED
#define XPRS_SOLSTATUS_OPTIMAL
#define XPRS_SOLSTATUS_NOTFOUND
#define XPRS_INTEGER
Initial version of this code was provided by RTE.