Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
xpress_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include "absl/strings/str_join.h"
26
27namespace operations_research {
28namespace math_opt {
29namespace {
30
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"));
37 }
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: ",
44 ProtoEnumToString(parameters.lp_algorithm())));
45 }
46 if (parameters.has_objective_limit()) {
47 warnings.emplace_back("XpressSolver does not support objective_limit yet");
48 }
49 if (parameters.has_best_bound_limit()) {
50 warnings.emplace_back("XpressSolver does not support best_bound_limit yet");
51 }
52 if (parameters.has_cutoff_limit()) {
53 warnings.emplace_back("XpressSolver does not support cutoff_limit yet");
54 }
55 if (!warnings.empty()) {
56 return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
57 }
58 return absl::OkStatus();
59}
60} // namespace
61
63 .integer_variables = SupportType::kNotSupported,
64 .multi_objectives = SupportType::kNotSupported,
65 .quadratic_objectives = SupportType::kSupported,
66 .quadratic_constraints = SupportType::kNotSupported,
67 .second_order_cone_constraints = SupportType::kNotSupported,
68 .sos1_constraints = SupportType::kNotSupported,
69 .sos2_constraints = SupportType::kNotSupported,
70 .indicator_constraints = SupportType::kNotSupported};
71
72absl::StatusOr<std::unique_ptr<XpressSolver>> XpressSolver::New(
73 const ModelProto& input_model, const SolverInterface::InitArgs& init_args) {
75 return absl::InvalidArgumentError("Xpress is not correctly installed.");
76 }
78 ModelIsSupported(input_model, kXpressSupportedStructures, "XPRESS"));
79
80 // We can add here extra checks that are not made in ModelIsSupported
81 // (for example, if XPRESS does not support multi-objective with quad terms)
82
83 ASSIGN_OR_RETURN(std::unique_ptr<Xpress> xpr,
84 Xpress::New(input_model.name()));
85 auto xpress_solver = absl::WrapUnique(new XpressSolver(std::move(xpr)));
86 RETURN_IF_ERROR(xpress_solver->LoadModel(input_model));
87 return xpress_solver;
88}
89
90absl::Status XpressSolver::LoadModel(const ModelProto& input_model) {
91 CHECK(xpress_ != nullptr);
92 RETURN_IF_ERROR(xpress_->SetProbName(input_model.name()));
93 RETURN_IF_ERROR(AddNewVariables(input_model.variables()));
94 RETURN_IF_ERROR(AddNewLinearConstraints(input_model.linear_constraints()));
95 RETURN_IF_ERROR(ChangeCoefficients(input_model.linear_constraint_matrix()));
96 RETURN_IF_ERROR(AddSingleObjective(input_model.objective()));
97 return absl::OkStatus();
98}
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);
107 variable_type[j] =
108 new_variables.integers(j) ? XPRS_INTEGER : XPRS_CONTINUOUS;
109 if (new_variables.integers(j)) {
110 is_mip_ = true;
111 return absl::UnimplementedError("XpressSolver does not handle MIPs yet");
112 }
113 }
114 RETURN_IF_ERROR(xpress_->AddVars({}, new_variables.lower_bounds(),
115 new_variables.upper_bounds(),
116 variable_type));
117
118 // Not adding names for performance (have to call XPRSaddnames)
119 // TODO: keep names in a cache and add them when needed
120
121 return absl::OkStatus();
122}
123
124XpressSolver::XpressSolver(std::unique_ptr<Xpress> g_xpress)
125 : xpress_(std::move(g_xpress)) {}
126
127absl::Status XpressSolver::AddNewLinearConstraints(
128 const LinearConstraintsProto& constraints) {
129 // TODO: we might be able to improve performance by setting coefs also
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 =
141 InsertKeyOrDie(&linear_constraints_map_, id);
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;
147 char sense = XPRS_EQUAL;
148 double rhs = 0.0;
149 double rng = 0.0;
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) {
153 sense = XPRS_LESS_EQUAL;
154 rhs = ub;
155 } else if (!lb_is_xprs_neg_inf && ub_is_xprs_pos_inf) {
156 sense = XPRS_GREATER_EQUAL;
157 rhs = lb;
158 } else if (lb == ub) {
159 sense = XPRS_EQUAL;
160 rhs = lb;
161 } else {
162 sense = XPRS_RANGE;
163 rhs = ub;
164 rng = ub - lb;
165 }
166 constraint_sense.emplace_back(sense);
167 constraint_rhs.emplace_back(rhs);
168 constraint_rng.emplace_back(rng);
169 }
170 // Add all constraints in one call.
171 return xpress_->AddConstrs(constraint_sense, constraint_rhs, constraint_rng);
172}
173
174absl::Status XpressSolver::AddSingleObjective(const ObjectiveProto& objective) {
175 // Sense
176 RETURN_IF_ERROR(xpress_->SetObjectiveSense(objective.maximize()));
177 is_maximize_ = objective.maximize();
178 // Linear terms
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));
183 }
184 RETURN_IF_ERROR(xpress_->SetLinearObjective(
185 objective.offset(), index, objective.linear_coefficients().values()));
186 // Quadratic terms
187 const int num_terms = objective.quadratic_coefficients().row_ids().size();
188 if (num_terms > 0) {
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);
198 // XPRESS supposes a 1/2 implicit multiplier to quadratic terms (see doc)
199 // We have to multiply it by 2 for diagonal terms
200 double m = first_var_index[k] == second_var_index[k] ? 2 : 1;
201 coefficients[k] = objective.quadratic_coefficients().coefficients(k) * m;
202 }
203 RETURN_IF_ERROR(xpress_->SetQuadraticObjective(
204 first_var_index, second_var_index, coefficients));
205 }
206 return absl::OkStatus();
207}
208
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) {
217 row_index.push_back(
218 linear_constraints_map_.at(matrix.row_ids(k)).constraint_index);
219 col_index.push_back(variables_map_.at(matrix.column_ids(k)));
220 }
221 return xpress_->ChgCoeffs(row_index, col_index, matrix.coefficients());
222}
223
224absl::StatusOr<SolveResultProto> XpressSolver::Solve(
225 const SolveParametersProto& parameters,
226 const ModelSolveParametersProto& model_parameters,
227 MessageCallback message_cb,
228 const CallbackRegistrationProto& callback_registration, Callback cb,
229 const SolveInterrupter* interrupter) {
231 model_parameters, kXpressSupportedStructures, "XPRESS"));
232 const absl::Time start = absl::Now();
233
235 /*supported_events=*/{}));
236
237 RETURN_IF_ERROR(CheckParameters(parameters));
238
239 // Check that bounds are not inverted just before solve
240 // XPRESS returns "infeasible" when bounds are inverted
241 {
242 ASSIGN_OR_RETURN(const InvertedBounds inv_bounds, ListInvertedBounds());
243 RETURN_IF_ERROR(inv_bounds.ToStatus());
244 }
245
246 // Set initial basis
247 if (model_parameters.has_initial_basis()) {
248 RETURN_IF_ERROR(SetXpressStartingBasis(model_parameters.initial_basis()));
249 }
250
251 RETURN_IF_ERROR(CallXpressSolve(parameters)) << "Error during XPRESS solve";
252
254 SolveResultProto solve_result,
255 ExtractSolveResultProto(start, model_parameters, parameters));
256
257 return solve_result;
258}
259
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;
265 return "p";
266 case LP_ALGORITHM_DUAL_SIMPLEX:
267 lp_algorithm_ = LP_ALGORITHM_DUAL_SIMPLEX;
268 return "d";
269 case LP_ALGORITHM_BARRIER:
270 lp_algorithm_ = LP_ALGORITHM_BARRIER;
271 return "b";
272 default:
273 // this makes XPRESS use default algorithm (XPRS_DEFAULTALG)
274 // but we have to figure out what it is for solution processing
275 auto default_alg = xpress_->GetIntControl(XPRS_DEFAULTALG);
276 switch (default_alg.value_or(-1)) {
277 case XPRS_ALG_PRIMAL:
278 lp_algorithm_ = LP_ALGORITHM_PRIMAL_SIMPLEX;
279 break;
280 case XPRS_ALG_DUAL:
281 lp_algorithm_ = LP_ALGORITHM_DUAL_SIMPLEX;
282 break;
283 case XPRS_ALG_BARRIER:
284 lp_algorithm_ = LP_ALGORITHM_BARRIER;
285 break;
286 default:
287 lp_algorithm_ = LP_ALGORITHM_UNSPECIFIED;
288 }
289 return "";
290 }
291}
292absl::Status XpressSolver::CallXpressSolve(
293 const SolveParametersProto& parameters) {
294 // Enable screen output right before solve
295 if (parameters.enable_output()) {
296 RETURN_IF_ERROR(xpress_->SetIntControl(XPRS_OUTPUTLOG, 1))
297 << "Unable to enable XPRESS logs";
298 }
299 // Solve
300 if (is_mip_) {
301 RETURN_IF_ERROR(xpress_->MipOptimize());
302 ASSIGN_OR_RETURN(xpress_mip_status_, xpress_->GetIntAttr(XPRS_MIPSTATUS));
303 } else {
304 RETURN_IF_ERROR(SetLpIterLimits(parameters))
305 << "Could not set iteration limits.";
306 RETURN_IF_ERROR(xpress_->LpOptimize(GetLpOptimizationFlags(parameters)));
307 ASSIGN_OR_RETURN(int primal_status, xpress_->GetIntAttr(XPRS_LPSTATUS));
308 ASSIGN_OR_RETURN(int dual_status, xpress_->GetDualStatus());
309 xpress_lp_status_ = {primal_status, dual_status};
310 }
311 // Post-solve
312 if (!(is_mip_ ? (xpress_mip_status_ == XPRS_MIP_OPTIMAL)
313 : (xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL))) {
314 RETURN_IF_ERROR(xpress_->PostSolve()) << "Post-solve failed in XPRESS";
315 }
316 // Disable screen output right after solve
317 if (parameters.enable_output()) {
318 RETURN_IF_ERROR(xpress_->SetIntControl(XPRS_OUTPUTLOG, 0))
319 << "Unable to disable XPRESS logs";
320 }
321 return absl::OkStatus();
322}
323
324absl::Status XpressSolver::SetLpIterLimits(
325 const SolveParametersProto& parameters) {
326 // If the user has set no limits, we still have to reset the limits
327 // explicitly to their default values, else the parameters could be kept
328 // between solves.
329 if (parameters.has_iteration_limit()) {
331 xpress_->SetIntControl(XPRS_LPITERLIMIT, parameters.iteration_limit()))
332 << "Could not set XPRS_LPITERLIMIT";
334 xpress_->SetIntControl(XPRS_BARITERLIMIT, parameters.iteration_limit()))
335 << "Could not set XPRS_BARITERLIMIT";
336 } else {
337 RETURN_IF_ERROR(xpress_->ResetIntControl(XPRS_LPITERLIMIT))
338 << "Could not reset XPRS_LPITERLIMIT to its default value";
339 RETURN_IF_ERROR(xpress_->ResetIntControl(XPRS_BARITERLIMIT))
340 << "Could not reset XPRS_BARITERLIMIT to its default value";
341 }
342 return absl::OkStatus();
343}
344
345absl::StatusOr<SolveResultProto> XpressSolver::ExtractSolveResultProto(
346 absl::Time start, const ModelSolveParametersProto& model_parameters,
347 const SolveParametersProto& solve_parameters) {
348 SolveResultProto result;
349 ASSIGN_OR_RETURN(SolutionProto solution,
350 GetSolution(model_parameters, solve_parameters));
351 *result.add_solutions() = std::move(solution);
352 ASSIGN_OR_RETURN(*result.mutable_solve_stats(), GetSolveStats(start));
353 ASSIGN_OR_RETURN(const double best_primal_bound, GetBestPrimalBound());
354 ASSIGN_OR_RETURN(const double best_dual_bound, GetBestDualBound());
356 *result.mutable_termination(),
357 ConvertTerminationReason(best_primal_bound, best_dual_bound));
358 return result;
359}
360
361absl::StatusOr<double> XpressSolver::GetBestPrimalBound() const {
362 if (lp_algorithm_ == LP_ALGORITHM_PRIMAL_SIMPLEX && isPrimalFeasible() ||
363 xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL) {
364 // When primal simplex algorithm is used, XPRESS uses LPOBJVAL to store the
365 // primal problem's objective value
366 return xpress_->GetDoubleAttr(XPRS_LPOBJVAL);
367 }
368 return is_maximize_ ? kMinusInf : kPlusInf;
369}
370
371absl::StatusOr<double> XpressSolver::GetBestDualBound() const {
372 if (lp_algorithm_ == LP_ALGORITHM_DUAL_SIMPLEX && isPrimalFeasible() ||
373 xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL) {
374 // When dual simplex algorithm is used, XPRESS uses LPOBJVAL to store the
375 // dual problem's objective value
376 return xpress_->GetDoubleAttr(XPRS_LPOBJVAL);
377 }
378 return is_maximize_ ? kPlusInf : kMinusInf;
379}
380
381absl::StatusOr<SolutionProto> XpressSolver::GetSolution(
382 const ModelSolveParametersProto& model_parameters,
383 const SolveParametersProto& solve_parameters) {
384 if (is_mip_) {
385 return absl::UnimplementedError("XpressSolver does not handle MIPs yet");
386 } else {
387 return GetLpSolution(model_parameters, solve_parameters);
388 }
389}
390
391absl::StatusOr<SolutionProto> XpressSolver::GetLpSolution(
392 const ModelSolveParametersProto& model_parameters,
393 const SolveParametersProto& solve_parameters) {
394 // Fetch all results from XPRESS
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);
400
401 auto hasSolution =
402 xpress_
403 ->GetLpSol(absl::MakeSpan(primals), absl::MakeSpan(duals),
404 absl::MakeSpan(reducedCosts))
405 .ok();
406
407 SolutionProto solution{};
408
409 if (isPrimalFeasible()) {
410 // Handle primal solution
411 solution.mutable_primal_solution()->set_feasibility_status(
412 getLpSolutionStatus());
413 ASSIGN_OR_RETURN(const double primalBound, GetBestPrimalBound());
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());
419 }
420
421 if (hasSolution) {
422 // Add dual solution even if not feasible
423 solution.mutable_dual_solution()->set_feasibility_status(
424 getDualSolutionStatus());
425 ASSIGN_OR_RETURN(const double dualBound, GetBestDualBound());
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());
435 }
436
437 // Get basis
438 ASSIGN_OR_RETURN(auto basis, GetBasisIfAvailable(solve_parameters));
439 if (basis.has_value()) {
440 *solution.mutable_basis() = std::move(*basis);
441 }
442 return solution;
443}
444
445bool XpressSolver::isPrimalFeasible() const {
446 if (is_mip_) {
447 return xpress_mip_status_ == XPRS_MIP_OPTIMAL ||
448 xpress_mip_status_ == XPRS_MIP_SOLUTION;
449 } else {
450 return xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL ||
451 xpress_lp_status_.primal_status == XPRS_LP_UNFINISHED;
452 }
453}
454
455bool XpressSolver::isDualFeasible() const {
456 if (is_mip_) {
457 return isPrimalFeasible();
458 }
459 return xpress_lp_status_.dual_status == XPRS_SOLSTATUS_OPTIMAL ||
460 xpress_lp_status_.dual_status == XPRS_SOLSTATUS_FEASIBLE ||
461 // When using dual simplex algorithm, if we interrupt it, dual_status
462 // is "not found" even if there is a solution. Using the following
463 // as a workaround for now
464 (lp_algorithm_ == LP_ALGORITHM_DUAL_SIMPLEX && isPrimalFeasible());
465}
466
467SolutionStatusProto XpressSolver::getLpSolutionStatus() const {
468 switch (xpress_lp_status_.primal_status) {
469 case XPRS_LP_OPTIMAL:
471 return SOLUTION_STATUS_FEASIBLE;
472 case XPRS_LP_INFEAS:
473 case XPRS_LP_CUTOFF:
476 return SOLUTION_STATUS_INFEASIBLE;
479 case XPRS_LP_UNSOLVED:
480 return SOLUTION_STATUS_UNDETERMINED;
481 default:
482 return SOLUTION_STATUS_UNSPECIFIED;
483 }
484}
485
486SolutionStatusProto XpressSolver::getDualSolutionStatus() const {
487 // When using dual simplex algorithm, if we interrupt it, dual_status
488 // is "not found" even if there is a solution. Using the following
489 // as a workaround for now
490 if (isDualFeasible()) {
491 return SOLUTION_STATUS_FEASIBLE;
492 }
493 switch (xpress_lp_status_.dual_status) {
496 return SOLUTION_STATUS_FEASIBLE;
498 return SOLUTION_STATUS_INFEASIBLE;
500 // when primal is unbounded, XPRESS returns unbounded for dual also (known
501 // issue). this is a temporary workaround
502 return (xpress_lp_status_.primal_status == XPRS_LP_UNBOUNDED)
503 ? SOLUTION_STATUS_INFEASIBLE
504 : SOLUTION_STATUS_UNDETERMINED;
506 return SOLUTION_STATUS_UNDETERMINED;
507 default:
508 return SOLUTION_STATUS_UNSPECIFIED;
509 }
510}
511
512inline BasisStatusProto XpressToMathOptBasisStatus(const int status,
513 bool isConstraint) {
514 // XPRESS row basis status is that of the slack variable
515 // For example, if the slack variable is at LB, the constraint is at UB
516 switch (status) {
517 case XPRS_BASIC:
518 return BASIS_STATUS_BASIC;
519 case XPRS_AT_LOWER:
520 return isConstraint ? BASIS_STATUS_AT_UPPER_BOUND
521 : BASIS_STATUS_AT_LOWER_BOUND;
522 case XPRS_AT_UPPER:
523 return isConstraint ? BASIS_STATUS_AT_LOWER_BOUND
524 : BASIS_STATUS_AT_UPPER_BOUND;
525 case XPRS_FREE_SUPER:
526 return BASIS_STATUS_FREE;
527 default:
528 return BASIS_STATUS_UNSPECIFIED;
529 }
530}
531
532inline int MathOptToXpressBasisStatus(const BasisStatusProto status,
533 bool isConstraint) {
534 // XPRESS row basis status is that of the slack variable
535 // For example, if the slack variable is at LB, the constraint is at UB
536 switch (status) {
537 case BASIS_STATUS_BASIC:
538 return XPRS_BASIC;
539 case BASIS_STATUS_AT_LOWER_BOUND:
540 return isConstraint ? XPRS_AT_UPPER : XPRS_AT_LOWER;
541 case BASIS_STATUS_AT_UPPER_BOUND:
542 return isConstraint ? XPRS_AT_LOWER : XPRS_AT_UPPER;
543 case BASIS_STATUS_FREE:
544 return XPRS_FREE_SUPER;
545 default:
546 return XPRS_FREE_SUPER;
547 }
548}
549
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)] =
554 MathOptToXpressBasisStatus(static_cast<BasisStatusProto>(value), false);
555 }
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)
560 .constraint_index] =
561 MathOptToXpressBasisStatus(static_cast<BasisStatusProto>(value), true);
562 }
563 return xpress_->SetStartingBasis(xpress_constr_basis_status,
564 xpress_var_basis_status);
565}
566
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;
571 if (!xpress_
572 ->GetBasis(xprs_constraint_basis_status, xprs_variable_basis_status)
573 .ok()) {
574 return std::nullopt;
575 }
576
577 BasisProto basis;
578 // Variable basis
579 for (auto [variable_id, xprs_variable_index] : variables_map_) {
580 basis.mutable_variable_status()->add_ids(variable_id);
581 const BasisStatusProto variable_status = XpressToMathOptBasisStatus(
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]));
587 }
588 basis.mutable_variable_status()->add_values(variable_status);
589 }
590
591 // Constraint basis
592 for (auto [constraint_id, xprs_ct_index] : linear_constraints_map_) {
593 basis.mutable_constraint_status()->add_ids(constraint_id);
594 const BasisStatusProto status = XpressToMathOptBasisStatus(
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]));
600 }
601 basis.mutable_constraint_status()->add_values(status);
602 }
603
604 // Dual basis
605 basis.set_basic_dual_feasibility(
606 isDualFeasible() ? SOLUTION_STATUS_FEASIBLE : SOLUTION_STATUS_INFEASIBLE);
607 return basis;
608}
609
610absl::StatusOr<SolveStatsProto> XpressSolver::GetSolveStats(
611 absl::Time start) const {
612 SolveStatsProto solve_stats;
613 CHECK_OK(util_time::EncodeGoogleApiProto(absl::Now() - start,
614 solve_stats.mutable_solve_time()));
615
616 // LP simplex iterations
617 {
618 ASSIGN_OR_RETURN(const int iters, xpress_->GetIntAttr(XPRS_SIMPLEXITER));
619 solve_stats.set_simplex_iterations(iters);
620 }
621 // LP barrier iterations
622 {
623 ASSIGN_OR_RETURN(const int iters, xpress_->GetIntAttr(XPRS_BARITER));
624 solve_stats.set_barrier_iterations(iters);
625 }
626
627 // TODO: complete these stats
628 return solve_stats;
629}
630
631template <typename T>
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)) {
640 result.add_ids(id);
641 result.add_values(value);
642 }
643 }
644}
645
646absl::StatusOr<TerminationProto> XpressSolver::ConvertTerminationReason(
647 double best_primal_bound, double best_dual_bound) const {
648 if (!is_mip_) {
649 switch (xpress_lp_status_.primal_status) {
651 return TerminateForReason(
652 is_maximize_, TERMINATION_REASON_OTHER_ERROR,
653 "Problem solve has not started (XPRS_LP_UNSTARTED)");
654 case XPRS_LP_OPTIMAL:
655 return OptimalTerminationProto(best_primal_bound, best_dual_bound);
656 case XPRS_LP_INFEAS:
658 is_maximize_, isDualFeasible() ? FEASIBILITY_STATUS_FEASIBLE
659 : FEASIBILITY_STATUS_UNDETERMINED);
660 case XPRS_LP_CUTOFF:
662 is_maximize_, "Objective worse than cutoff (XPRS_LP_CUTOFF)");
664 // TODO: add support for more limit types here (this only works for LP
665 // iterations limit for now)
667 is_maximize_, LIMIT_ITERATION, best_primal_bound, best_dual_bound,
668 "Solve did not finish (XPRS_LP_UNFINISHED)");
670 return UnboundedTerminationProto(is_maximize_,
671 "Xpress status XPRS_LP_UNBOUNDED");
674 is_maximize_, "Cutoff in dual (XPRS_LP_CUTOFF_IN_DUAL)");
675 case XPRS_LP_UNSOLVED:
676 return TerminateForReason(
677 is_maximize_, TERMINATION_REASON_NUMERICAL_ERROR,
678 "Problem could not be solved due to numerical issues "
679 "(XPRS_LP_UNSOLVED)");
681 return TerminateForReason(is_maximize_, TERMINATION_REASON_OTHER_ERROR,
682 "Problem contains quadratic data, which is "
683 "not convex (XPRS_LP_NONCONVEX)");
684 default:
685 return absl::InternalError(
686 absl::StrCat("Missing Xpress LP status code case: ",
687 xpress_lp_status_.primal_status));
688 }
689 } else {
690 return absl::UnimplementedError("XpressSolver does not handle MIPs yet");
691 }
692}
693
694absl::StatusOr<bool> XpressSolver::Update(
695 const ModelUpdateProto& model_update) {
696 // Not implemented yet
697 return false;
698}
699
700absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
701XpressSolver::ComputeInfeasibleSubsystem(const SolveParametersProto& parameters,
702 MessageCallback message_cb,
703 const SolveInterrupter* interrupter) {
704 return absl::UnimplementedError(
705 "XpressSolver cannot compute infeasible subsystem yet");
706}
707
708absl::StatusOr<InvertedBounds> XpressSolver::ListInvertedBounds() const {
709 InvertedBounds inverted_bounds;
710 {
711 ASSIGN_OR_RETURN(const std::vector<double> var_lbs, xpress_->GetVarLb());
712 ASSIGN_OR_RETURN(const std::vector<double> var_ubs, xpress_->GetVarUb());
713 for (const auto& [id, index] : variables_map_) {
714 if (var_lbs[index] > var_ubs[index]) {
715 inverted_bounds.variables.push_back(id);
716 }
717 }
718 }
719 // We could have used XPRSgetrhsrange to check if one is negative. However,
720 // XPRSaddrows ignores the sign of the RHS range and takes the absolute value
721 // in all cases. So we need to do the following checks on the internal model.
722 for (const auto& [id, cstr_data] : linear_constraints_map_) {
723 if (cstr_data.lower_bound > cstr_data.upper_bound) {
724 inverted_bounds.linear_constraints.push_back(id);
725 }
726 }
727 // Above code have inserted ids in non-stable order.
728 std::sort(inverted_bounds.variables.begin(), inverted_bounds.variables.end());
729 std::sort(inverted_bounds.linear_constraints.begin(),
730 inverted_bounds.linear_constraints.end());
731 return inverted_bounds;
732}
733
735} // namespace math_opt
736} // namespace operations_research
#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 &parameters, 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 &parameters, 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.
Definition g_xpress.cc:62
auto & InsertKeyOrDie(Collection *const collection, const typename Collection::value_type::first_type &key)
Definition map_util.h:178
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
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 &registration, 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)
Definition proto_utils.h:50
bool XpressIsCorrectlyInstalled()
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition protoutil.h:27
#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.
#define XPRS_SIMPLEXITER
#define XPRS_LPSTATUS
#define XPRS_LP_CUTOFF_IN_DUAL
#define XPRS_LP_INFEAS
#define XPRS_MIP_SOLUTION
#define XPRS_MIP_OPTIMAL
#define XPRS_BASIC
#define XPRS_MIPSTATUS
#define XPRS_LP_NONCONVEX
#define XPRS_FREE_SUPER
#define XPRS_ALG_BARRIER
#define XPRS_GREATER_EQUAL
#define XPRS_LP_CUTOFF
#define XPRS_EQUAL
#define XPRS_LESS_EQUAL
#define XPRS_SOLSTATUS_FEASIBLE
#define XPRS_AT_LOWER
#define XPRS_LP_UNFINISHED
#define XPRS_OUTPUTLOG
#define XPRS_LP_OPTIMAL
#define XPRS_LP_UNSTARTED
#define XPRS_SOLSTATUS_UNBOUNDED
#define XPRS_RANGE
#define XPRS_SOLSTATUS_INFEASIBLE
#define XPRS_LPITERLIMIT
#define XPRS_AT_UPPER
#define XPRS_BARITERLIMIT
#define XPRS_BARITER
#define XPRS_LP_UNBOUNDED
#define XPRS_SOLSTATUS_OPTIMAL
#define XPRS_DEFAULTALG
#define XPRS_LPOBJVAL
#define XPRS_ALG_PRIMAL
#define XPRS_SOLSTATUS_NOTFOUND
#define XPRS_ALG_DUAL
#define XPRS_LP_UNSOLVED
#define XPRS_CONTINUOUS
#define XPRS_INTEGER
Initial version of this code was provided by RTE.