16#include "absl/strings/str_join.h"
31absl::Status CheckParameters(
const SolveParametersProto& parameters) {
32 std::vector<std::string> warnings;
33 if (parameters.has_threads() && parameters.threads() > 1) {
34 warnings.push_back(absl::StrCat(
35 "XpressSolver only supports parameters.threads = 1; value ",
36 parameters.threads(),
" is not supported"));
38 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED &&
39 parameters.lp_algorithm() != LP_ALGORITHM_PRIMAL_SIMPLEX &&
40 parameters.lp_algorithm() != LP_ALGORITHM_DUAL_SIMPLEX &&
41 parameters.lp_algorithm() != LP_ALGORITHM_BARRIER) {
42 warnings.emplace_back(absl::StrCat(
43 "XpressSolver does not support the 'lp_algorithm' parameter value: ",
46 if (parameters.has_objective_limit()) {
47 warnings.emplace_back(
"XpressSolver does not support objective_limit yet");
49 if (parameters.has_best_bound_limit()) {
50 warnings.emplace_back(
"XpressSolver does not support best_bound_limit yet");
52 if (parameters.has_cutoff_limit()) {
53 warnings.emplace_back(
"XpressSolver does not support cutoff_limit yet");
55 if (!warnings.empty()) {
56 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
58 return absl::OkStatus();
75 return absl::InvalidArgumentError(
"Xpress is not correctly installed.");
85 auto xpress_solver = absl::WrapUnique(
new XpressSolver(std::move(xpr)));
90absl::Status XpressSolver::LoadModel(
const ModelProto& input_model) {
91 CHECK(xpress_ !=
nullptr);
94 RETURN_IF_ERROR(AddNewLinearConstraints(input_model.linear_constraints()));
95 RETURN_IF_ERROR(ChangeCoefficients(input_model.linear_constraint_matrix()));
97 return absl::OkStatus();
99absl::Status XpressSolver::AddNewVariables(
100 const VariablesProto& new_variables) {
101 const int num_new_variables = new_variables.lower_bounds().size();
102 std::vector<char> variable_type(num_new_variables);
103 int n_variables = xpress_->GetNumberOfVariables();
104 for (
int j = 0; j < num_new_variables; ++j) {
105 const VarId
id = new_variables.ids(j);
106 InsertOrDie(&variables_map_,
id, j + n_variables);
109 if (new_variables.integers(j)) {
111 return absl::UnimplementedError(
"XpressSolver does not handle MIPs yet");
115 new_variables.upper_bounds(),
121 return absl::OkStatus();
124XpressSolver::XpressSolver(std::unique_ptr<Xpress> g_xpress)
125 : xpress_(std::move(g_xpress)) {}
127absl::Status XpressSolver::AddNewLinearConstraints(
128 const LinearConstraintsProto& constraints) {
130 const int num_new_constraints = constraints.lower_bounds().size();
131 std::vector<char> constraint_sense;
132 constraint_sense.reserve(num_new_constraints);
133 std::vector<double> constraint_rhs;
134 constraint_rhs.reserve(num_new_constraints);
135 std::vector<double> constraint_rng;
136 constraint_rng.reserve(num_new_constraints);
137 int n_constraints = xpress_->GetNumberOfConstraints();
138 for (
int i = 0;
i < num_new_constraints; ++
i) {
139 const int64_t
id = constraints.ids(i);
140 LinearConstraintData& constraint_data =
142 const double lb = constraints.lower_bounds(i);
143 const double ub = constraints.upper_bounds(i);
144 constraint_data.lower_bound = lb;
145 constraint_data.upper_bound = ub;
146 constraint_data.constraint_index =
i + n_constraints;
150 const bool lb_is_xprs_neg_inf = lb <= kMinusInf;
151 const bool ub_is_xprs_pos_inf = ub >= kPlusInf;
152 if (lb_is_xprs_neg_inf && !ub_is_xprs_pos_inf) {
155 }
else if (!lb_is_xprs_neg_inf && ub_is_xprs_pos_inf) {
158 }
else if (lb == ub) {
166 constraint_sense.emplace_back(sense);
167 constraint_rhs.emplace_back(rhs);
168 constraint_rng.emplace_back(rng);
171 return xpress_->AddConstrs(constraint_sense, constraint_rhs, constraint_rng);
174absl::Status XpressSolver::AddSingleObjective(
const ObjectiveProto& objective) {
177 is_maximize_ = objective.maximize();
179 std::vector<int> index;
180 index.reserve(objective.linear_coefficients().ids_size());
181 for (
const int64_t
id : objective.linear_coefficients().ids()) {
182 index.push_back(variables_map_.at(
id));
185 objective.offset(), index, objective.linear_coefficients().values()));
187 const int num_terms = objective.quadratic_coefficients().row_ids().size();
189 std::vector<int> first_var_index(num_terms);
190 std::vector<int> second_var_index(num_terms);
191 std::vector<double> coefficients(num_terms);
192 for (
int k = 0; k < num_terms; ++k) {
193 const int64_t row_id = objective.quadratic_coefficients().row_ids(k);
194 const int64_t column_id =
195 objective.quadratic_coefficients().column_ids(k);
196 first_var_index[k] = variables_map_.at(row_id);
197 second_var_index[k] = variables_map_.at(column_id);
200 double m = first_var_index[k] == second_var_index[k] ? 2 : 1;
201 coefficients[k] = objective.quadratic_coefficients().coefficients(k) * m;
204 first_var_index, second_var_index, coefficients));
206 return absl::OkStatus();
209absl::Status XpressSolver::ChangeCoefficients(
210 const SparseDoubleMatrixProto& matrix) {
211 const int num_coefficients = matrix.row_ids().size();
212 std::vector<int> row_index;
213 row_index.reserve(num_coefficients);
214 std::vector<int> col_index;
215 col_index.reserve(num_coefficients);
216 for (
int k = 0; k < num_coefficients; ++k) {
218 linear_constraints_map_.at(matrix.row_ids(k)).constraint_index);
219 col_index.push_back(variables_map_.at(matrix.column_ids(k)));
221 return xpress_->ChgCoeffs(row_index, col_index, matrix.coefficients());
225 const SolveParametersProto& parameters,
226 const ModelSolveParametersProto& model_parameters,
228 const CallbackRegistrationProto& callback_registration,
Callback cb,
232 const absl::Time start = absl::Now();
247 if (model_parameters.has_initial_basis()) {
248 RETURN_IF_ERROR(SetXpressStartingBasis(model_parameters.initial_basis()));
251 RETURN_IF_ERROR(CallXpressSolve(parameters)) <<
"Error during XPRESS solve";
254 SolveResultProto solve_result,
255 ExtractSolveResultProto(start, model_parameters, parameters));
260std::string XpressSolver::GetLpOptimizationFlags(
261 const SolveParametersProto& parameters) {
262 switch (parameters.lp_algorithm()) {
263 case LP_ALGORITHM_PRIMAL_SIMPLEX:
264 lp_algorithm_ = LP_ALGORITHM_PRIMAL_SIMPLEX;
266 case LP_ALGORITHM_DUAL_SIMPLEX:
267 lp_algorithm_ = LP_ALGORITHM_DUAL_SIMPLEX;
269 case LP_ALGORITHM_BARRIER:
270 lp_algorithm_ = LP_ALGORITHM_BARRIER;
276 switch (default_alg.value_or(-1)) {
278 lp_algorithm_ = LP_ALGORITHM_PRIMAL_SIMPLEX;
281 lp_algorithm_ = LP_ALGORITHM_DUAL_SIMPLEX;
284 lp_algorithm_ = LP_ALGORITHM_BARRIER;
287 lp_algorithm_ = LP_ALGORITHM_UNSPECIFIED;
292absl::Status XpressSolver::CallXpressSolve(
293 const SolveParametersProto& parameters) {
295 if (parameters.enable_output()) {
297 <<
"Unable to enable XPRESS logs";
305 <<
"Could not set iteration limits.";
306 RETURN_IF_ERROR(xpress_->LpOptimize(GetLpOptimizationFlags(parameters)));
309 xpress_lp_status_ = {primal_status, dual_status};
314 RETURN_IF_ERROR(xpress_->PostSolve()) <<
"Post-solve failed in XPRESS";
317 if (parameters.enable_output()) {
319 <<
"Unable to disable XPRESS logs";
321 return absl::OkStatus();
324absl::Status XpressSolver::SetLpIterLimits(
325 const SolveParametersProto& parameters) {
329 if (parameters.has_iteration_limit()) {
332 <<
"Could not set XPRS_LPITERLIMIT";
335 <<
"Could not set XPRS_BARITERLIMIT";
338 <<
"Could not reset XPRS_LPITERLIMIT to its default value";
340 <<
"Could not reset XPRS_BARITERLIMIT to its default value";
342 return absl::OkStatus();
345absl::StatusOr<SolveResultProto> XpressSolver::ExtractSolveResultProto(
346 absl::Time start,
const ModelSolveParametersProto& model_parameters,
347 const SolveParametersProto& solve_parameters) {
348 SolveResultProto result;
350 GetSolution(model_parameters, solve_parameters));
351 *result.add_solutions() = std::move(
solution);
356 *result.mutable_termination(),
357 ConvertTerminationReason(best_primal_bound, best_dual_bound));
361absl::StatusOr<double> XpressSolver::GetBestPrimalBound()
const {
362 if (lp_algorithm_ == LP_ALGORITHM_PRIMAL_SIMPLEX && isPrimalFeasible() ||
368 return is_maximize_ ? kMinusInf : kPlusInf;
371absl::StatusOr<double> XpressSolver::GetBestDualBound()
const {
372 if (lp_algorithm_ == LP_ALGORITHM_DUAL_SIMPLEX && isPrimalFeasible() ||
378 return is_maximize_ ? kPlusInf : kMinusInf;
381absl::StatusOr<SolutionProto> XpressSolver::GetSolution(
382 const ModelSolveParametersProto& model_parameters,
383 const SolveParametersProto& solve_parameters) {
385 return absl::UnimplementedError(
"XpressSolver does not handle MIPs yet");
387 return GetLpSolution(model_parameters, solve_parameters);
391absl::StatusOr<SolutionProto> XpressSolver::GetLpSolution(
392 const ModelSolveParametersProto& model_parameters,
393 const SolveParametersProto& solve_parameters) {
395 int nVars = xpress_->GetNumberOfVariables();
396 int nCons = xpress_->GetNumberOfConstraints();
397 std::vector<double> primals(nVars);
398 std::vector<double> duals(nCons);
399 std::vector<double> reducedCosts(nVars);
403 ->GetLpSol(absl::MakeSpan(primals), absl::MakeSpan(duals),
404 absl::MakeSpan(reducedCosts))
409 if (isPrimalFeasible()) {
411 solution.mutable_primal_solution()->set_feasibility_status(
412 getLpSolutionStatus());
414 solution.mutable_primal_solution()->set_objective_value(primalBound);
415 XpressVectorToSparseDoubleVector(
416 primals, variables_map_,
417 *
solution.mutable_primal_solution()->mutable_variable_values(),
418 model_parameters.variable_values_filter());
423 solution.mutable_dual_solution()->set_feasibility_status(
424 getDualSolutionStatus());
426 solution.mutable_dual_solution()->set_objective_value(dualBound);
427 XpressVectorToSparseDoubleVector(
428 duals, linear_constraints_map_,
429 *
solution.mutable_dual_solution()->mutable_dual_values(),
430 model_parameters.dual_values_filter());
431 XpressVectorToSparseDoubleVector(
432 reducedCosts, variables_map_,
433 *
solution.mutable_dual_solution()->mutable_reduced_costs(),
434 model_parameters.reduced_costs_filter());
439 if (basis.has_value()) {
440 *
solution.mutable_basis() = std::move(*basis);
445bool XpressSolver::isPrimalFeasible()
const {
455bool XpressSolver::isDualFeasible()
const {
457 return isPrimalFeasible();
464 (lp_algorithm_ == LP_ALGORITHM_DUAL_SIMPLEX && isPrimalFeasible());
467SolutionStatusProto XpressSolver::getLpSolutionStatus()
const {
468 switch (xpress_lp_status_.primal_status) {
471 return SOLUTION_STATUS_FEASIBLE;
476 return SOLUTION_STATUS_INFEASIBLE;
480 return SOLUTION_STATUS_UNDETERMINED;
482 return SOLUTION_STATUS_UNSPECIFIED;
486SolutionStatusProto XpressSolver::getDualSolutionStatus()
const {
490 if (isDualFeasible()) {
491 return SOLUTION_STATUS_FEASIBLE;
493 switch (xpress_lp_status_.dual_status) {
496 return SOLUTION_STATUS_FEASIBLE;
498 return SOLUTION_STATUS_INFEASIBLE;
503 ? SOLUTION_STATUS_INFEASIBLE
504 : SOLUTION_STATUS_UNDETERMINED;
506 return SOLUTION_STATUS_UNDETERMINED;
508 return SOLUTION_STATUS_UNSPECIFIED;
518 return BASIS_STATUS_BASIC;
520 return isConstraint ? BASIS_STATUS_AT_UPPER_BOUND
521 : BASIS_STATUS_AT_LOWER_BOUND;
523 return isConstraint ? BASIS_STATUS_AT_LOWER_BOUND
524 : BASIS_STATUS_AT_UPPER_BOUND;
526 return BASIS_STATUS_FREE;
528 return BASIS_STATUS_UNSPECIFIED;
537 case BASIS_STATUS_BASIC:
539 case BASIS_STATUS_AT_LOWER_BOUND:
541 case BASIS_STATUS_AT_UPPER_BOUND:
543 case BASIS_STATUS_FREE:
550absl::Status XpressSolver::SetXpressStartingBasis(
const BasisProto& basis) {
551 std::vector<int> xpress_var_basis_status(xpress_->GetNumberOfVariables());
552 for (
const auto [
id, value] :
MakeView(basis.variable_status())) {
553 xpress_var_basis_status[variables_map_.
at(
id)] =
556 std::vector<int> xpress_constr_basis_status(
557 xpress_->GetNumberOfConstraints());
558 for (
const auto [
id, value] :
MakeView(basis.constraint_status())) {
559 xpress_constr_basis_status[linear_constraints_map_.
at(
id)
563 return xpress_->SetStartingBasis(xpress_constr_basis_status,
564 xpress_var_basis_status);
567absl::StatusOr<std::optional<BasisProto>> XpressSolver::GetBasisIfAvailable(
568 const SolveParametersProto& parameters) {
569 std::vector<int> xprs_variable_basis_status;
570 std::vector<int> xprs_constraint_basis_status;
572 ->GetBasis(xprs_constraint_basis_status, xprs_variable_basis_status)
579 for (
auto [variable_id, xprs_variable_index] : variables_map_) {
580 basis.mutable_variable_status()->add_ids(variable_id);
582 xprs_variable_basis_status[xprs_variable_index],
false);
583 if (variable_status == BASIS_STATUS_UNSPECIFIED) {
584 return absl::InternalError(
585 absl::StrCat(
"Invalid Xpress variable basis status: ",
586 xprs_variable_basis_status[xprs_variable_index]));
588 basis.mutable_variable_status()->add_values(variable_status);
592 for (
auto [constraint_id, xprs_ct_index] : linear_constraints_map_) {
593 basis.mutable_constraint_status()->add_ids(constraint_id);
595 xprs_constraint_basis_status[xprs_ct_index.constraint_index],
true);
596 if (status == BASIS_STATUS_UNSPECIFIED) {
597 return absl::InternalError(absl::StrCat(
598 "Invalid Xpress constraint basis status: ",
599 xprs_constraint_basis_status[xprs_ct_index.constraint_index]));
601 basis.mutable_constraint_status()->add_values(status);
605 basis.set_basic_dual_feasibility(
606 isDualFeasible() ? SOLUTION_STATUS_FEASIBLE : SOLUTION_STATUS_INFEASIBLE);
610absl::StatusOr<SolveStatsProto> XpressSolver::GetSolveStats(
611 absl::Time start)
const {
612 SolveStatsProto solve_stats;
614 solve_stats.mutable_solve_time()));
619 solve_stats.set_simplex_iterations(iters);
624 solve_stats.set_barrier_iterations(iters);
632void XpressSolver::XpressVectorToSparseDoubleVector(
633 absl::Span<const double> xpress_values,
const T& map,
634 SparseDoubleVectorProto& result,
635 const SparseVectorFilterProto& filter)
const {
636 SparseVectorFilterPredicate predicate(filter);
637 for (
auto [
id, xpress_data] : map) {
638 const double value = xpress_values[get_model_index(xpress_data)];
639 if (predicate.AcceptsAndUpdate(
id, value)) {
641 result.add_values(value);
646absl::StatusOr<TerminationProto> XpressSolver::ConvertTerminationReason(
647 double best_primal_bound,
double best_dual_bound)
const {
649 switch (xpress_lp_status_.primal_status) {
652 is_maximize_, TERMINATION_REASON_OTHER_ERROR,
653 "Problem solve has not started (XPRS_LP_UNSTARTED)");
658 is_maximize_, isDualFeasible() ? FEASIBILITY_STATUS_FEASIBLE
659 : FEASIBILITY_STATUS_UNDETERMINED);
662 is_maximize_,
"Objective worse than cutoff (XPRS_LP_CUTOFF)");
667 is_maximize_, LIMIT_ITERATION, best_primal_bound, best_dual_bound,
668 "Solve did not finish (XPRS_LP_UNFINISHED)");
671 "Xpress status XPRS_LP_UNBOUNDED");
674 is_maximize_,
"Cutoff in dual (XPRS_LP_CUTOFF_IN_DUAL)");
677 is_maximize_, TERMINATION_REASON_NUMERICAL_ERROR,
678 "Problem could not be solved due to numerical issues "
679 "(XPRS_LP_UNSOLVED)");
682 "Problem contains quadratic data, which is "
683 "not convex (XPRS_LP_NONCONVEX)");
685 return absl::InternalError(
686 absl::StrCat(
"Missing Xpress LP status code case: ",
687 xpress_lp_status_.primal_status));
690 return absl::UnimplementedError(
"XpressSolver does not handle MIPs yet");
695 const ModelUpdateProto& model_update) {
700absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
704 return absl::UnimplementedError(
705 "XpressSolver cannot compute infeasible subsystem yet");
708absl::StatusOr<InvertedBounds> XpressSolver::ListInvertedBounds()
const {
713 for (
const auto& [
id, index] : variables_map_) {
714 if (var_lbs[index] > var_ubs[index]) {
722 for (
const auto& [
id, cstr_data] : linear_constraints_map_) {
723 if (cstr_data.lower_bound > cstr_data.upper_bound) {
731 return inverted_bounds;
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
mapped_type & at(const key_arg< K > &key)
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(const std::string &model_name)
Creates a new Xpress.
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)
constexpr SupportedProblemStructures kXpressSupportedStructures
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
BasisStatusProto XpressToMathOptBasisStatus(const int status, bool isConstraint)
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
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.
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.