Google OR-Tools v9.14
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 <algorithm>
17#include <cstdint>
18#include <memory>
19#include <optional>
20#include <string>
21#include <utility>
22#include <vector>
23
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"
45
46namespace operations_research {
47namespace math_opt {
48namespace {
49
50absl::Status CheckParameters(const SolveParametersProto& parameters) {
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"));
56 }
57 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED &&
58 parameters.lp_algorithm() != LP_ALGORITHM_PRIMAL_SIMPLEX &&
59 parameters.lp_algorithm() != LP_ALGORITHM_DUAL_SIMPLEX &&
60 parameters.lp_algorithm() != LP_ALGORITHM_BARRIER) {
61 warnings.emplace_back(absl::StrCat(
62 "XpressSolver does not support the 'lp_algorithm' parameter value: ",
63 ProtoEnumToString(parameters.lp_algorithm())));
64 }
65 if (parameters.has_objective_limit()) {
66 warnings.emplace_back("XpressSolver does not support objective_limit yet");
67 }
68 if (parameters.has_best_bound_limit()) {
69 warnings.emplace_back("XpressSolver does not support best_bound_limit yet");
70 }
71 if (parameters.has_cutoff_limit()) {
72 warnings.emplace_back("XpressSolver does not support cutoff_limit yet");
73 }
74 if (!warnings.empty()) {
75 return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
76 }
77 return absl::OkStatus();
78}
79} // namespace
80
82 .integer_variables = SupportType::kNotSupported,
83 .multi_objectives = SupportType::kNotSupported,
84 .quadratic_objectives = SupportType::kSupported,
85 .quadratic_constraints = SupportType::kNotSupported,
86 .second_order_cone_constraints = SupportType::kNotSupported,
87 .sos1_constraints = SupportType::kNotSupported,
88 .sos2_constraints = SupportType::kNotSupported,
89 .indicator_constraints = SupportType::kNotSupported};
90
91absl::StatusOr<std::unique_ptr<XpressSolver>> XpressSolver::New(
92 const ModelProto& model, const InitArgs&) {
94 return absl::InvalidArgumentError("Xpress is not correctly installed.");
95 }
98
99 // We can add here extra checks that are not made in ModelIsSupported
100 // (for example, if XPRESS does not support multi-objective with quad terms)
101
102 ASSIGN_OR_RETURN(auto xpr, Xpress::New(model.name()));
103 auto xpress_solver = absl::WrapUnique(new XpressSolver(std::move(xpr)));
104 RETURN_IF_ERROR(xpress_solver->LoadModel(model));
105 return xpress_solver;
106}
107
108absl::Status XpressSolver::LoadModel(const ModelProto& input_model) {
109 CHECK(xpress_ != nullptr);
110 RETURN_IF_ERROR(xpress_->SetProbName(input_model.name()));
111 RETURN_IF_ERROR(AddNewVariables(input_model.variables()));
112 RETURN_IF_ERROR(AddNewLinearConstraints(input_model.linear_constraints()));
113 RETURN_IF_ERROR(ChangeCoefficients(input_model.linear_constraint_matrix()));
114 RETURN_IF_ERROR(AddSingleObjective(input_model.objective()));
115 return absl::OkStatus();
116}
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);
124 gtl::InsertOrDie(&variables_map_, id, j + n_variables);
125 variable_type[j] =
126 new_variables.integers(j) ? XPRS_INTEGER : XPRS_CONTINUOUS;
127 if (new_variables.integers(j)) {
128 is_mip_ = true;
129 return absl::UnimplementedError("XpressSolver does not handle MIPs yet");
130 }
131 }
132 RETURN_IF_ERROR(xpress_->AddVars({}, new_variables.lower_bounds(),
133 new_variables.upper_bounds(),
134 variable_type));
135
136 // Not adding names for performance (have to call XPRSaddnames)
137 // TODO: keep names in a cache and add them when needed
138
139 return absl::OkStatus();
140}
141
142XpressSolver::XpressSolver(std::unique_ptr<Xpress> g_xpress)
143 : xpress_(std::move(g_xpress)) {}
144
145absl::Status XpressSolver::AddNewLinearConstraints(
146 const LinearConstraintsProto& constraints) {
147 // TODO: we might be able to improve performance by setting coefs also
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 =
159 gtl::InsertKeyOrDie(&linear_constraints_map_, id);
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;
165 char sense = XPRS_EQUAL;
166 double rhs = 0.0;
167 double rng = 0.0;
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) {
171 sense = XPRS_LESS_EQUAL;
172 rhs = ub;
173 } else if (!lb_is_xprs_neg_inf && ub_is_xprs_pos_inf) {
174 sense = XPRS_GREATER_EQUAL;
175 rhs = lb;
176 } else if (lb == ub) {
177 sense = XPRS_EQUAL;
178 rhs = lb;
179 } else {
180 sense = XPRS_RANGE;
181 rhs = ub;
182 rng = ub - lb;
183 }
184 constraint_sense.emplace_back(sense);
185 constraint_rhs.emplace_back(rhs);
186 constraint_rng.emplace_back(rng);
187 }
188 // Add all constraints in one call.
189 return xpress_->AddConstrs(constraint_sense, constraint_rhs, constraint_rng);
190}
191
192absl::Status XpressSolver::AddSingleObjective(const ObjectiveProto& objective) {
193 // Sense
194 RETURN_IF_ERROR(xpress_->SetObjectiveSense(objective.maximize()));
195 is_maximize_ = objective.maximize();
196 // Linear terms
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));
201 }
202 RETURN_IF_ERROR(xpress_->SetLinearObjective(
203 objective.offset(), index, objective.linear_coefficients().values()));
204 // Quadratic terms
205 const int num_terms = objective.quadratic_coefficients().row_ids().size();
206 if (num_terms > 0) {
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);
216 // XPRESS supposes a 1/2 implicit multiplier to quadratic terms (see doc)
217 // We have to multiply it by 2 for diagonal terms
218 double m = first_var_index[k] == second_var_index[k] ? 2 : 1;
219 coefficients[k] = objective.quadratic_coefficients().coefficients(k) * m;
220 }
221 RETURN_IF_ERROR(xpress_->SetQuadraticObjective(
222 first_var_index, second_var_index, coefficients));
223 }
224 return absl::OkStatus();
225}
226
227absl::Status XpressSolver::ChangeCoefficients(
228 const SparseDoubleMatrixProto& matrix) {
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) {
235 row_index.push_back(
236 linear_constraints_map_.at(matrix.row_ids(k)).constraint_index);
237 col_index.push_back(variables_map_.at(matrix.column_ids(k)));
238 }
239 return xpress_->ChgCoeffs(row_index, col_index, matrix.coefficients());
240}
241
242absl::StatusOr<SolveResultProto> XpressSolver::Solve(
243 const SolveParametersProto& parameters,
244 const ModelSolveParametersProto& model_parameters, MessageCallback,
245 const CallbackRegistrationProto& callback_registration, Callback,
246 const SolveInterrupter*) {
248 model_parameters, kXpressSupportedStructures, "XPRESS"));
249 const absl::Time start = absl::Now();
250
252 /*supported_events=*/{}));
253
254 RETURN_IF_ERROR(CheckParameters(parameters));
255
256 // Check that bounds are not inverted just before solve
257 // XPRESS returns "infeasible" when bounds are inverted
258 {
259 ASSIGN_OR_RETURN(const InvertedBounds inverted_bounds,
260 ListInvertedBounds());
261 RETURN_IF_ERROR(inverted_bounds.ToStatus());
262 }
263
264 // Set initial basis
265 if (model_parameters.has_initial_basis()) {
266 RETURN_IF_ERROR(SetXpressStartingBasis(model_parameters.initial_basis()));
267 }
268
269 RETURN_IF_ERROR(CallXpressSolve(parameters)) << "Error during XPRESS solve";
270
272 SolveResultProto solve_result,
273 ExtractSolveResultProto(start, model_parameters, parameters));
274
275 return solve_result;
276}
277
278std::string XpressSolver::GetLpOptimizationFlags(
279 const SolveParametersProto& parameters) {
280 switch (parameters.lp_algorithm()) {
282 lp_algorithm_ = LP_ALGORITHM_PRIMAL_SIMPLEX;
283 return "p";
285 lp_algorithm_ = LP_ALGORITHM_DUAL_SIMPLEX;
286 return "d";
288 lp_algorithm_ = LP_ALGORITHM_BARRIER;
289 return "b";
290 default:
291 // this makes XPRESS use default algorithm (XPRS_DEFAULTALG)
292 // but we have to figure out what it is for solution processing
293 auto default_alg = xpress_->GetIntControl(XPRS_DEFAULTALG);
294 switch (default_alg.value_or(-1)) {
295 case XPRS_ALG_PRIMAL:
296 lp_algorithm_ = LP_ALGORITHM_PRIMAL_SIMPLEX;
297 break;
298 case XPRS_ALG_DUAL:
299 lp_algorithm_ = LP_ALGORITHM_DUAL_SIMPLEX;
300 break;
301 case XPRS_ALG_BARRIER:
302 lp_algorithm_ = LP_ALGORITHM_BARRIER;
303 break;
304 default:
305 lp_algorithm_ = LP_ALGORITHM_UNSPECIFIED;
306 }
307 return "";
308 }
309}
310absl::Status XpressSolver::CallXpressSolve(
311 const SolveParametersProto& parameters) {
312 // Enable screen output right before solve
313 if (parameters.enable_output()) {
314 RETURN_IF_ERROR(xpress_->SetIntControl(XPRS_OUTPUTLOG, 1))
315 << "Unable to enable XPRESS logs";
316 }
317 // Solve
318 if (is_mip_) {
319 RETURN_IF_ERROR(xpress_->MipOptimize());
320 ASSIGN_OR_RETURN(xpress_mip_status_, xpress_->GetIntAttr(XPRS_MIPSTATUS));
321 } else {
322 RETURN_IF_ERROR(SetLpIterLimits(parameters))
323 << "Could not set iteration limits.";
324 RETURN_IF_ERROR(xpress_->LpOptimize(GetLpOptimizationFlags(parameters)));
325 ASSIGN_OR_RETURN(int primal_status, xpress_->GetIntAttr(XPRS_LPSTATUS));
326 ASSIGN_OR_RETURN(int dual_status, xpress_->GetDualStatus());
327 xpress_lp_status_ = {primal_status, dual_status};
328 }
329 // Post-solve
330 if (!(is_mip_ ? (xpress_mip_status_ == XPRS_MIP_OPTIMAL)
331 : (xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL))) {
332 RETURN_IF_ERROR(xpress_->PostSolve()) << "Post-solve failed in XPRESS";
333 }
334 // Disable screen output right after solve
335 if (parameters.enable_output()) {
336 RETURN_IF_ERROR(xpress_->SetIntControl(XPRS_OUTPUTLOG, 0))
337 << "Unable to disable XPRESS logs";
338 }
339 return absl::OkStatus();
340}
341
342absl::Status XpressSolver::SetLpIterLimits(
343 const SolveParametersProto& parameters) {
344 // If the user has set no limits, we still have to reset the limits
345 // explicitly to their default values, else the parameters could be kept
346 // between solves.
347 if (parameters.has_iteration_limit()) {
348 RETURN_IF_ERROR(xpress_->SetIntControl(
349 XPRS_LPITERLIMIT, static_cast<int>(parameters.iteration_limit())))
350 << "Could not set XPRS_LPITERLIMIT";
351 RETURN_IF_ERROR(xpress_->SetIntControl(
352 XPRS_BARITERLIMIT, static_cast<int>(parameters.iteration_limit())))
353 << "Could not set XPRS_BARITERLIMIT";
354 } else {
355 RETURN_IF_ERROR(xpress_->ResetIntControl(XPRS_LPITERLIMIT))
356 << "Could not reset XPRS_LPITERLIMIT to its default value";
357 RETURN_IF_ERROR(xpress_->ResetIntControl(XPRS_BARITERLIMIT))
358 << "Could not reset XPRS_BARITERLIMIT to its default value";
359 }
360 return absl::OkStatus();
361}
362
363absl::StatusOr<SolveResultProto> XpressSolver::ExtractSolveResultProto(
364 absl::Time start, const ModelSolveParametersProto& model_parameters,
365 const SolveParametersProto& solve_parameters) {
366 SolveResultProto result;
367 ASSIGN_OR_RETURN(SolutionProto solution,
368 GetSolution(model_parameters, solve_parameters));
369 *result.add_solutions() = std::move(solution);
370 ASSIGN_OR_RETURN(*result.mutable_solve_stats(), GetSolveStats(start));
371 ASSIGN_OR_RETURN(const double best_primal_bound, GetBestPrimalBound());
372 ASSIGN_OR_RETURN(const double best_dual_bound, GetBestDualBound());
374 *result.mutable_termination(),
375 ConvertTerminationReason(best_primal_bound, best_dual_bound));
376 return result;
377}
378
379absl::StatusOr<double> XpressSolver::GetBestPrimalBound() const {
380 if ((lp_algorithm_ == LP_ALGORITHM_PRIMAL_SIMPLEX) &&
381 (isPrimalFeasible() ||
382 xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL)) {
383 // When primal simplex algorithm is used, XPRESS uses LPOBJVAL to store the
384 // primal problem's objective value
385 return xpress_->GetDoubleAttr(XPRS_LPOBJVAL);
386 }
387 return is_maximize_ ? kMinusInf : kPlusInf;
388}
389
390absl::StatusOr<double> XpressSolver::GetBestDualBound() const {
391 if ((lp_algorithm_ == LP_ALGORITHM_DUAL_SIMPLEX) &&
392 (isPrimalFeasible() ||
393 xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL)) {
394 // When dual simplex algorithm is used, XPRESS uses LPOBJVAL to store the
395 // dual problem's objective value
396 return xpress_->GetDoubleAttr(XPRS_LPOBJVAL);
397 }
398 return is_maximize_ ? kPlusInf : kMinusInf;
399}
400
401absl::StatusOr<SolutionProto> XpressSolver::GetSolution(
402 const ModelSolveParametersProto& model_parameters,
403 const SolveParametersProto& solve_parameters) {
404 if (is_mip_) {
405 return absl::UnimplementedError("XpressSolver does not handle MIPs yet");
406 } else {
407 return GetLpSolution(model_parameters, solve_parameters);
408 }
409}
410
411absl::StatusOr<SolutionProto> XpressSolver::GetLpSolution(
412 const ModelSolveParametersProto& model_parameters,
413 const SolveParametersProto& solve_parameters) {
414 // Fetch all results from XPRESS
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);
420
421 auto hasSolution =
422 xpress_
423 ->GetLpSol(absl::MakeSpan(primals), absl::MakeSpan(duals),
424 absl::MakeSpan(reducedCosts))
425 .ok();
426
427 SolutionProto solution{};
428
429 if (isPrimalFeasible()) {
430 // Handle primal solution
431 solution.mutable_primal_solution()->set_feasibility_status(
432 getLpSolutionStatus());
433 ASSIGN_OR_RETURN(const double primalBound, GetBestPrimalBound());
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());
439 }
440
441 if (hasSolution) {
442 // Add dual solution even if not feasible
443 solution.mutable_dual_solution()->set_feasibility_status(
444 getDualSolutionStatus());
445 ASSIGN_OR_RETURN(const double dualBound, GetBestDualBound());
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());
455 }
456
457 // Get basis
458 ASSIGN_OR_RETURN(auto basis, GetBasisIfAvailable(solve_parameters));
459 if (basis.has_value()) {
460 *solution.mutable_basis() = std::move(*basis);
461 }
462 return solution;
463}
464
465bool XpressSolver::isPrimalFeasible() const {
466 if (is_mip_) {
467 return xpress_mip_status_ == XPRS_MIP_OPTIMAL ||
468 xpress_mip_status_ == XPRS_MIP_SOLUTION;
469 } else {
470 return xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL ||
471 xpress_lp_status_.primal_status == XPRS_LP_UNFINISHED;
472 }
473}
474
475bool XpressSolver::isDualFeasible() const {
476 if (is_mip_) {
477 return isPrimalFeasible();
478 }
479 return xpress_lp_status_.dual_status == XPRS_SOLSTATUS_OPTIMAL ||
480 xpress_lp_status_.dual_status == XPRS_SOLSTATUS_FEASIBLE ||
481 // When using dual simplex algorithm, if we interrupt it, dual_status
482 // is "not found" even if there is a solution. Using the following
483 // as a workaround for now
484 (lp_algorithm_ == LP_ALGORITHM_DUAL_SIMPLEX && isPrimalFeasible());
485}
486
487SolutionStatusProto XpressSolver::getLpSolutionStatus() const {
488 switch (xpress_lp_status_.primal_status) {
489 case XPRS_LP_OPTIMAL:
492 case XPRS_LP_INFEAS:
493 case XPRS_LP_CUTOFF:
499 case XPRS_LP_UNSOLVED:
501 default:
503 }
504}
505
506SolutionStatusProto XpressSolver::getDualSolutionStatus() const {
507 // When using dual simplex algorithm, if we interrupt it, dual_status
508 // is "not found" even if there is a solution. Using the following
509 // as a workaround for now
510 if (isDualFeasible()) {
512 }
513 switch (xpress_lp_status_.dual_status) {
520 // when primal is unbounded, XPRESS returns unbounded for dual also (known
521 // issue). this is a temporary workaround
522 return (xpress_lp_status_.primal_status == XPRS_LP_UNBOUNDED)
527 default:
529 }
530}
531
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 XPRS_BASIC:
538 return BASIS_STATUS_BASIC;
539 case XPRS_AT_LOWER:
540 return isConstraint ? BASIS_STATUS_AT_UPPER_BOUND
542 case XPRS_AT_UPPER:
543 return isConstraint ? BASIS_STATUS_AT_LOWER_BOUND
545 case XPRS_FREE_SUPER:
546 return BASIS_STATUS_FREE;
547 default:
549 }
550}
551
553 bool isConstraint) {
554 // XPRESS row basis status is that of the slack variable
555 // For example, if the slack variable is at LB, the constraint is at UB
556 switch (status) {
558 return XPRS_BASIC;
560 return isConstraint ? XPRS_AT_UPPER : XPRS_AT_LOWER;
562 return isConstraint ? XPRS_AT_LOWER : XPRS_AT_UPPER;
564 return XPRS_FREE_SUPER;
565 default:
566 return XPRS_FREE_SUPER;
567 }
568}
569
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)] =
574 MathOptToXpressBasisStatus(static_cast<BasisStatusProto>(value), false);
575 }
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)
580 .constraint_index] =
581 MathOptToXpressBasisStatus(static_cast<BasisStatusProto>(value), true);
582 }
583 return xpress_->SetStartingBasis(xpress_constr_basis_status,
584 xpress_var_basis_status);
585}
586
587absl::StatusOr<std::optional<BasisProto>> XpressSolver::GetBasisIfAvailable(
588 const SolveParametersProto&) {
589 std::vector<int> xprs_variable_basis_status;
590 std::vector<int> xprs_constraint_basis_status;
591 if (!xpress_
592 ->GetBasis(xprs_constraint_basis_status, xprs_variable_basis_status)
593 .ok()) {
594 return std::nullopt;
595 }
596
597 BasisProto basis;
598 // Variable basis
599 for (auto [variable_id, xprs_variable_index] : variables_map_) {
600 basis.mutable_variable_status()->add_ids(variable_id);
601 const BasisStatusProto variable_status = XpressToMathOptBasisStatus(
602 xprs_variable_basis_status[xprs_variable_index], false);
603 if (variable_status == BASIS_STATUS_UNSPECIFIED) {
604 return absl::InternalError(
605 absl::StrCat("Invalid Xpress variable basis status: ",
606 xprs_variable_basis_status[xprs_variable_index]));
607 }
608 basis.mutable_variable_status()->add_values(variable_status);
609 }
610
611 // Constraint basis
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);
616 if (status == BASIS_STATUS_UNSPECIFIED) {
617 return absl::InternalError(absl::StrCat(
618 "Invalid Xpress constraint basis status: ",
619 xprs_constraint_basis_status[xprs_ct_index.constraint_index]));
620 }
621 basis.mutable_constraint_status()->add_values(status);
622 }
623
624 // Dual basis
625 basis.set_basic_dual_feasibility(
627 return basis;
628}
629
630absl::StatusOr<SolveStatsProto> XpressSolver::GetSolveStats(
631 absl::Time start) const {
632 SolveStatsProto solve_stats;
633 CHECK_OK(util_time::EncodeGoogleApiProto(absl::Now() - start,
634 solve_stats.mutable_solve_time()));
635
636 // LP simplex iterations
637 {
638 ASSIGN_OR_RETURN(const int iters, xpress_->GetIntAttr(XPRS_SIMPLEXITER));
639 solve_stats.set_simplex_iterations(iters);
640 }
641 // LP barrier iterations
642 {
643 ASSIGN_OR_RETURN(const int iters, xpress_->GetIntAttr(XPRS_BARITER));
644 solve_stats.set_barrier_iterations(iters);
645 }
646
647 // TODO: complete these stats
648 return solve_stats;
649}
650
651template <typename T>
652void XpressSolver::XpressVectorToSparseDoubleVector(
653 absl::Span<const double> xpress_values, const T& map,
655 const SparseVectorFilterProto& filter) const {
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)) {
660 result.add_ids(id);
661 result.add_values(value);
662 }
663 }
664}
665
666absl::StatusOr<TerminationProto> XpressSolver::ConvertTerminationReason(
667 double best_primal_bound, double best_dual_bound) const {
668 if (!is_mip_) {
669 switch (xpress_lp_status_.primal_status) {
671 return TerminateForReason(
672 is_maximize_, TERMINATION_REASON_OTHER_ERROR,
673 "Problem solve has not started (XPRS_LP_UNSTARTED)");
674 case XPRS_LP_OPTIMAL:
675 return OptimalTerminationProto(best_primal_bound, best_dual_bound);
676 case XPRS_LP_INFEAS:
678 is_maximize_, isDualFeasible() ? FEASIBILITY_STATUS_FEASIBLE
680 case XPRS_LP_CUTOFF:
682 is_maximize_, "Objective worse than cutoff (XPRS_LP_CUTOFF)");
684 // TODO: add support for more limit types here (this only works for LP
685 // iterations limit for now)
687 is_maximize_, LIMIT_ITERATION, best_primal_bound, best_dual_bound,
688 "Solve did not finish (XPRS_LP_UNFINISHED)");
690 return UnboundedTerminationProto(is_maximize_,
691 "Xpress status XPRS_LP_UNBOUNDED");
694 is_maximize_, "Cutoff in dual (XPRS_LP_CUTOFF_IN_DUAL)");
695 case XPRS_LP_UNSOLVED:
696 return TerminateForReason(
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)");
704 default:
705 return absl::InternalError(
706 absl::StrCat("Missing Xpress LP status code case: ",
707 xpress_lp_status_.primal_status));
708 }
709 } else {
710 return absl::UnimplementedError("XpressSolver does not handle MIPs yet");
711 }
712}
713
714absl::StatusOr<bool> XpressSolver::Update(const ModelUpdateProto&) {
715 // Not implemented yet
716 return false;
717}
718
719absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
722 const SolveInterrupter*) {
723 return absl::UnimplementedError(
724 "XPRESS does not provide a method to compute an infeasible subsystem");
725}
726
727absl::StatusOr<InvertedBounds> XpressSolver::ListInvertedBounds() const {
728 InvertedBounds inverted_bounds;
729 {
730 ASSIGN_OR_RETURN(const std::vector<double> var_lbs, xpress_->GetVarLb());
731 ASSIGN_OR_RETURN(const std::vector<double> var_ubs, xpress_->GetVarUb());
732 for (const auto& [id, index] : variables_map_) {
733 if (var_lbs[index] > var_ubs[index]) {
734 inverted_bounds.variables.push_back(id);
735 }
736 }
737 }
738 // We could have used XPRSgetrhsrange to check if one is negative. However,
739 // XPRSaddrows ignores the sign of the RHS range and takes the absolute value
740 // in all cases. So we need to do the following checks on the internal model.
741 for (const auto& [id, cstr_data] : linear_constraints_map_) {
742 if (cstr_data.lower_bound > cstr_data.upper_bound) {
743 inverted_bounds.linear_constraints.push_back(id);
744 }
745 }
746 // Above code have inserted ids in non-stable order.
747 std::sort(inverted_bounds.variables.begin(), inverted_bounds.variables.end());
748 std::sort(inverted_bounds.linear_constraints.begin(),
749 inverted_bounds.linear_constraints.end());
750 return inverted_bounds;
751}
752
754} // namespace math_opt
755} // namespace operations_research
#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
Definition model.pb.h:4404
const ::operations_research::math_opt::LinearConstraintsProto & linear_constraints() const
Definition model.pb.h:4629
const ::std::string & name() const
Definition model.pb.h:4329
const ::operations_research::math_opt::ObjectiveProto & objective() const
Definition model.pb.h:4502
const ::operations_research::math_opt::SparseDoubleMatrixProto & linear_constraint_matrix() const
Definition model.pb.h:4722
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 &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(absl::string_view model_name)
Creates a new Xpress.
Definition g_xpress.cc:62
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition map_util.h:159
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:63
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.