Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
glop_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 <atomic>
18#include <cstdint>
19#include <functional>
20#include <memory>
21#include <string>
22#include <vector>
23
24#include "absl/base/nullability.h"
25#include "absl/cleanup/cleanup.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/log/check.h"
28#include "absl/log/log.h"
29#include "absl/memory/memory.h"
30#include "absl/status/status.h"
31#include "absl/status/statusor.h"
32#include "absl/strings/str_cat.h"
33#include "absl/strings/str_join.h"
34#include "absl/strings/str_split.h"
35#include "absl/strings/string_view.h"
36#include "absl/time/clock.h"
37#include "absl/time/time.h"
38#include "absl/types/span.h"
67
68namespace operations_research {
69namespace math_opt {
70
71namespace {
72
73constexpr SupportedProblemStructures kGlopSupportedStructures = {};
74
75absl::string_view SafeName(const VariablesProto& variables, int index) {
76 if (variables.names().empty()) {
77 return {};
78 }
79 return variables.names(index);
80}
81
82absl::string_view SafeName(const LinearConstraintsProto& linear_constraints,
83 int index) {
84 if (linear_constraints.names().empty()) {
85 return {};
86 }
87 return linear_constraints.names(index);
88}
89
90absl::StatusOr<TerminationProto> BuildTermination(
91 const glop::ProblemStatus status,
92 const SolveInterrupter* absl_nullable const interrupter,
93 const bool is_maximize, const double objective_value) {
94 switch (status) {
96 return OptimalTerminationProto(objective_value, objective_value);
99 is_maximize,
100 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED);
103 is_maximize,
104 /*dual_feasibility_status=*/FEASIBILITY_STATUS_FEASIBLE);
106 return UnboundedTerminationProto(is_maximize);
109 is_maximize,
110 /*dual_feasibility_status=*/FEASIBILITY_STATUS_INFEASIBLE);
113 is_maximize,
114 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED);
116 // Glop may flip the `interrupt_solve` atomic when it is terminated for a
117 // reason other than interruption so we should ignore its value. Instead
118 // we use the interrupter.
119 // A primal feasible solution is only returned for PRIMAL_FEASIBLE (see
120 // comments in FillSolution).
122 is_maximize, interrupter != nullptr && interrupter->IsInterrupted()
126 // Glop may flip the `interrupt_solve` atomic when it is terminated for a
127 // reason other than interruption so we should ignore its value. Instead
128 // we use the interrupter.
129 // A primal feasible solution is only returned for PRIMAL_FEASIBLE (see
130 // comments in FillSolution).
132 is_maximize,
133 interrupter != nullptr && interrupter->IsInterrupted()
136 objective_value);
138 // Glop may flip the `interrupt_solve` atomic when it is terminated for a
139 // reason other than interruption so we should ignore its value. Instead
140 // we use the interrupter.
141 // A primal feasible solution is only returned for PRIMAL_FEASIBLE (see
142 // comments in FillSolution).
144 is_maximize,
145 interrupter != nullptr && interrupter->IsInterrupted()
148 objective_value);
153 return absl::InternalError(
154 absl::StrCat("Unexpected GLOP termination reason: ",
156 }
157 LOG(FATAL) << "Unimplemented GLOP termination reason: "
159}
160
161// Returns an InvalidArgumentError if the provided parameters are invalid.
162absl::Status ValidateGlopParameters(const glop::GlopParameters& parameters) {
163 const std::string error = glop::ValidateParameters(parameters);
164 if (!error.empty()) {
166 << "invalid GlopParameters: " << error;
167 }
168 return absl::OkStatus();
169}
170
171} // namespace
172
173GlopSolver::GlopSolver() : linear_program_(), lp_solver_() {}
174
175void GlopSolver::AddVariables(const VariablesProto& variables) {
176 for (int i = 0; i < NumVariables(variables); ++i) {
177 const glop::ColIndex col_index = linear_program_.CreateNewVariable();
178 linear_program_.SetVariableBounds(col_index, variables.lower_bounds(i),
179 variables.upper_bounds(i));
180 linear_program_.SetVariableName(col_index, SafeName(variables, i));
181 gtl::InsertOrDie(&variables_, variables.ids(i), col_index);
182 }
183}
184
185// Note that this relies on the fact that when variable/constraint
186// are deleted, Glop re-index everything by compacting the
187// index domain in a stable way.
188template <typename IndexType>
190
191 IndexType num_indices,
192 absl::flat_hash_map<int64_t, IndexType>& id_index_map) {
194 num_indices.value(), IndexType(0));
195 IndexType new_index(0);
196 for (IndexType index(0); index < num_indices; ++index) {
197 if (indices_to_delete[index]) {
198 // Mark deleted index
199 new_indices[index] = IndexType(-1);
200 } else {
201 new_indices[index] = new_index;
202 ++new_index;
203 }
204 }
205 for (auto it = id_index_map.begin(); it != id_index_map.end();) {
206 IndexType index = it->second;
207 if (indices_to_delete[index]) {
208 // This safely deletes the entry and moves the iterator one step ahead.
209 id_index_map.erase(it++);
210 } else {
211 it->second = new_indices[index];
212 ++it;
213 }
214 }
215}
216
217void GlopSolver::DeleteVariables(absl::Span<const int64_t> ids_to_delete) {
218 const glop::ColIndex num_cols = linear_program_.num_variables();
219 glop::StrictITIVector<glop::ColIndex, bool> columns_to_delete(num_cols,
220 false);
221 for (const int64_t deleted_variable_id : ids_to_delete) {
222 columns_to_delete[variables_.at(deleted_variable_id)] = true;
223 }
224 linear_program_.DeleteColumns(columns_to_delete);
225 UpdateIdIndexMap<glop::ColIndex>(columns_to_delete, num_cols, variables_);
226}
227
228void GlopSolver::DeleteLinearConstraints(
229 absl::Span<const int64_t> ids_to_delete) {
230 const glop::RowIndex num_rows = linear_program_.num_constraints();
231 glop::DenseBooleanColumn rows_to_delete(num_rows, false);
232 for (const int64_t deleted_constraint_id : ids_to_delete) {
233 rows_to_delete[linear_constraints_.at(deleted_constraint_id)] = true;
234 }
235 linear_program_.DeleteRows(rows_to_delete);
236 UpdateIdIndexMap<glop::RowIndex>(rows_to_delete, num_rows,
237 linear_constraints_);
238}
239
240void GlopSolver::AddLinearConstraints(
241 const LinearConstraintsProto& linear_constraints) {
242 for (int i = 0; i < NumConstraints(linear_constraints); ++i) {
243 const glop::RowIndex row_index = linear_program_.CreateNewConstraint();
244 linear_program_.SetConstraintBounds(row_index,
245 linear_constraints.lower_bounds(i),
246 linear_constraints.upper_bounds(i));
247 linear_program_.SetConstraintName(row_index,
248 SafeName(linear_constraints, i));
249 gtl::InsertOrDie(&linear_constraints_, linear_constraints.ids(i),
250 row_index);
251 }
252}
253
254void GlopSolver::SetOrUpdateObjectiveCoefficients(
255 const SparseDoubleVectorProto& linear_objective_coefficients) {
256 for (int i = 0; i < linear_objective_coefficients.ids_size(); ++i) {
257 const glop::ColIndex col_index =
258 variables_.at(linear_objective_coefficients.ids(i));
259 linear_program_.SetObjectiveCoefficient(
260 col_index, linear_objective_coefficients.values(i));
261 }
262}
263
264void GlopSolver::SetOrUpdateConstraintMatrix(
265 const SparseDoubleMatrixProto& linear_constraint_matrix) {
266 for (int j = 0; j < NumMatrixNonzeros(linear_constraint_matrix); ++j) {
267 const glop::ColIndex col_index =
268 variables_.at(linear_constraint_matrix.column_ids(j));
269 const glop::RowIndex row_index =
270 linear_constraints_.at(linear_constraint_matrix.row_ids(j));
271 const double coefficient = linear_constraint_matrix.coefficients(j);
272 linear_program_.SetCoefficient(row_index, col_index, coefficient);
273 }
274}
275
276void GlopSolver::UpdateVariableBounds(
277 const VariableUpdatesProto& variable_updates) {
278 for (const auto [id, lb] : MakeView(variable_updates.lower_bounds())) {
279 const auto col_index = variables_.at(id);
280 linear_program_.SetVariableBounds(
281 col_index, lb, linear_program_.variable_upper_bounds()[col_index]);
282 }
283 for (const auto [id, ub] : MakeView(variable_updates.upper_bounds())) {
284 const auto col_index = variables_.at(id);
285 linear_program_.SetVariableBounds(
286 col_index, linear_program_.variable_lower_bounds()[col_index], ub);
287 }
288}
289
290void GlopSolver::UpdateLinearConstraintBounds(
291 const LinearConstraintUpdatesProto& linear_constraint_updates) {
292 for (const auto [id, lb] :
293 MakeView(linear_constraint_updates.lower_bounds())) {
294 const auto row_index = linear_constraints_.at(id);
295 linear_program_.SetConstraintBounds(
296 row_index, lb, linear_program_.constraint_upper_bounds()[row_index]);
297 }
298 for (const auto [id, ub] :
299 MakeView(linear_constraint_updates.upper_bounds())) {
300 const auto row_index = linear_constraints_.at(id);
301 linear_program_.SetConstraintBounds(
302 row_index, linear_program_.constraint_lower_bounds()[row_index], ub);
303 }
304}
305
306absl::StatusOr<glop::GlopParameters> GlopSolver::MergeSolveParameters(
307 const SolveParametersProto& solve_parameters,
308 const bool setting_initial_basis, const bool has_message_callback,
309 const bool is_maximization) {
310 // Validate first the user specific Glop parameters.
311 RETURN_IF_ERROR(ValidateGlopParameters(solve_parameters.glop()))
312 << "invalid SolveParametersProto.glop value";
313
314 glop::GlopParameters result = solve_parameters.glop();
315 std::vector<std::string> warnings;
316 if (!result.has_max_time_in_seconds() && solve_parameters.has_time_limit()) {
317 const absl::Duration time_limit =
318 util_time::DecodeGoogleApiProto(solve_parameters.time_limit()).value();
319 result.set_max_time_in_seconds(absl::ToDoubleSeconds(time_limit));
320 }
321 if (has_message_callback) {
322 // If we have a message callback, we must set log_search_progress to get any
323 // logs. We ignore the user's input on specific solver parameters here since
324 // it would be confusing to accept a callback but never call it.
325 result.set_log_search_progress(true);
326
327 // We don't want the logs to be also printed to stdout when we have a
328 // message callback. Here we ignore the user input since message callback
329 // can be used in the context of a server and printing to stdout could be a
330 // problem.
331 result.set_log_to_stdout(false);
332 } else if (!result.has_log_search_progress()) {
333 result.set_log_search_progress(solve_parameters.enable_output());
334 }
335 if (!result.has_num_omp_threads() && solve_parameters.has_threads()) {
336 result.set_num_omp_threads(solve_parameters.threads());
337 }
338 if (!result.has_random_seed() && solve_parameters.has_random_seed()) {
339 const int random_seed = std::max(0, solve_parameters.random_seed());
340 result.set_random_seed(random_seed);
341 }
342 if (!result.has_max_number_of_iterations() &&
343 solve_parameters.iteration_limit()) {
344 result.set_max_number_of_iterations(solve_parameters.iteration_limit());
345 }
346 if (solve_parameters.has_node_limit()) {
347 warnings.emplace_back("GLOP does snot support 'node_limit' parameter");
348 }
349 if (!result.has_use_dual_simplex() &&
350 solve_parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
351 switch (solve_parameters.lp_algorithm()) {
353 result.set_use_dual_simplex(false);
354 break;
356 result.set_use_dual_simplex(true);
357 break;
358 default:
359 warnings.emplace_back(absl::StrCat(
360 "GLOP does not support the 'lp_algorithm' parameter value: ",
361 ProtoEnumToString(solve_parameters.lp_algorithm())));
362 }
363 }
364 if (!result.has_use_scaling() && !result.has_scaling_method() &&
365 solve_parameters.scaling() != EMPHASIS_UNSPECIFIED) {
366 switch (solve_parameters.scaling()) {
367 case EMPHASIS_OFF:
368 result.set_use_scaling(false);
369 break;
370 case EMPHASIS_LOW:
371 case EMPHASIS_MEDIUM:
372 case EMPHASIS_HIGH:
374 result.set_use_scaling(true);
376 break;
377 default:
378 LOG(FATAL) << "Scaling emphasis: "
379 << ProtoEnumToString(solve_parameters.scaling())
380 << " unknown, error setting GLOP parameters";
381 }
382 }
383 if (setting_initial_basis) {
384 result.set_use_preprocessing(false);
385 } else if (!result.has_use_preprocessing() &&
386 solve_parameters.presolve() != EMPHASIS_UNSPECIFIED) {
387 switch (solve_parameters.presolve()) {
388 case EMPHASIS_OFF:
389 result.set_use_preprocessing(false);
390 break;
391 case EMPHASIS_LOW:
392 case EMPHASIS_MEDIUM:
393 case EMPHASIS_HIGH:
395 result.set_use_preprocessing(true);
396 break;
397 default:
398 LOG(FATAL) << "Presolve emphasis: "
399 << ProtoEnumToString(solve_parameters.presolve())
400 << " unknown, error setting GLOP parameters";
401 }
402 }
403 if (solve_parameters.cuts() != EMPHASIS_UNSPECIFIED) {
404 warnings.push_back(absl::StrCat(
405 "GLOP does not support 'cuts' parameters, but cuts was set to: ",
406 ProtoEnumToString(solve_parameters.cuts())));
407 }
408 if (solve_parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
409 warnings.push_back(
410 absl::StrCat("GLOP does not support 'heuristics' parameter, but "
411 "heuristics was set to: ",
412 ProtoEnumToString(solve_parameters.heuristics())));
413 }
414 if (solve_parameters.has_cutoff_limit()) {
415 warnings.push_back("GLOP does not support 'cutoff_limit' parameter");
416 }
417 // Solver stops once optimal objective is proven strictly greater than limit.
418 // limit.
419 const auto set_upper_limit_if_missing = [&result](const double limit) {
420 if (!result.has_objective_upper_limit()) {
421 result.set_objective_upper_limit(limit);
422 }
423 };
424 // Solver stops once optimal objective is proven strictly less than limit.
425 const auto set_lower_limit_if_missing = [&result](const double limit) {
426 if (!result.has_objective_lower_limit()) {
427 result.set_objective_lower_limit(limit);
428 }
429 };
430 if (solve_parameters.has_objective_limit()) {
431 if (is_maximization) {
432 set_upper_limit_if_missing(solve_parameters.objective_limit());
433 } else {
434 set_lower_limit_if_missing(solve_parameters.objective_limit());
435 }
436 }
437 if (solve_parameters.has_best_bound_limit()) {
438 if (is_maximization) {
439 set_lower_limit_if_missing(solve_parameters.best_bound_limit());
440 } else {
441 set_upper_limit_if_missing(solve_parameters.best_bound_limit());
442 }
443 }
444 if (solve_parameters.has_solution_limit()) {
445 warnings.push_back("GLOP does not support 'solution_limit' parameter");
446 }
447 if (!warnings.empty()) {
448 return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
449 }
450
451 // Validate the result of the merge. If the parameters are not valid, this is
452 // an internal error from MathOpt as user specified Glop parameters have been
453 // validated at the beginning of this function. Thus the invalid values are
454 // values translated from solve_parameters and this code should not produce
455 // invalid parameters.
456 RETURN_IF_ERROR(ValidateGlopParameters(result))
457 << "invalid GlopParameters generated from SolveParametersProto";
458
459 return result;
460}
461
462template <typename IndexType>
464 absl::Span<const int64_t> ids_in_order,
465 const absl::flat_hash_map<int64_t, IndexType>& id_map,
467 const SparseVectorFilterProto& filter) {
468 SparseVectorFilterPredicate predicate(filter);
470 for (const int64_t variable_id : ids_in_order) {
471 const double value = values[id_map.at(variable_id)];
472 if (predicate.AcceptsAndUpdate(variable_id, value)) {
473 result.add_ids(variable_id);
474 result.add_values(value);
475 }
476 }
477 return result;
478}
479
480// ValueType should be glop's VariableStatus or ConstraintStatus.
481template <typename ValueType>
482BasisStatusProto FromGlopBasisStatus(const ValueType glop_basis_status) {
483 switch (glop_basis_status) {
484 case ValueType::BASIC:
486 case ValueType::FIXED_VALUE:
488 case ValueType::AT_LOWER_BOUND:
490 case ValueType::AT_UPPER_BOUND:
492 case ValueType::FREE:
494 }
496}
497
498template <typename IndexType, typename ValueType>
500 absl::Span<const int64_t> ids_in_order,
501 const absl::flat_hash_map<int64_t, IndexType>& id_map,
504 for (const int64_t variable_id : ids_in_order) {
505 const ValueType value = values[id_map.at(variable_id)];
506 result.add_ids(variable_id);
507 result.add_values(FromGlopBasisStatus(value));
508 }
509 return result;
510}
511
512// ValueType should be glop's VariableStatus or ConstraintStatus.
513template <typename ValueType>
514ValueType ToGlopBasisStatus(const BasisStatusProto basis_status) {
515 switch (basis_status) {
517 return ValueType::BASIC;
519 return ValueType::FIXED_VALUE;
521 return ValueType::AT_LOWER_BOUND;
523 return ValueType::AT_UPPER_BOUND;
525 return ValueType::FREE;
526 default:
527 LOG(FATAL) << "Unexpected invalid initial_basis.";
528 return ValueType::FREE;
529 }
530}
531
532template <typename T>
533std::vector<int64_t> GetSortedIs(
534 const absl::flat_hash_map<int64_t, T>& id_map) {
535 std::vector<int64_t> sorted;
536 sorted.reserve(id_map.size());
537 for (const auto& entry : id_map) {
538 sorted.emplace_back(entry.first);
539 }
540 std::sort(sorted.begin(), sorted.end());
541 return sorted;
542}
543
544// Returns a vector of containing the MathOpt id of each row or column. Here T
545// is either (Col|Row)Index and id_map is expected to be
546// GlopSolver::(linear_constraints_|variables_).
547template <typename T>
549 const absl::flat_hash_map<int64_t, T>& id_map) {
550 // Guard value used to identify not-yet-set elements of index_to_id.
551 constexpr int64_t kEmptyId = -1;
552 glop::StrictITIVector<T, int64_t> index_to_id(T(id_map.size()), kEmptyId);
553 for (const auto& [id, index] : id_map) {
554 CHECK(index >= 0 && index < index_to_id.size()) << index;
555 CHECK_EQ(index_to_id[index], kEmptyId);
556 index_to_id[index] = id;
557 }
558
559 // At this point, index_to_id can't contain any kEmptyId values since
560 // index_to_id.size() == id_map.size() and we modified id_map.size() elements
561 // in the loop, after checking that the modified element was changed by a
562 // previous iteration.
563 return index_to_id;
564}
565
566InvertedBounds GlopSolver::ListInvertedBounds() const {
567 // Identify rows and columns by index first.
568 std::vector<glop::ColIndex> inverted_columns;
569 const glop::ColIndex num_cols = linear_program_.num_variables();
570 for (glop::ColIndex col(0); col < num_cols; ++col) {
571 if (linear_program_.variable_lower_bounds()[col] >
572 linear_program_.variable_upper_bounds()[col]) {
573 inverted_columns.push_back(col);
574 }
575 }
576 std::vector<glop::RowIndex> inverted_rows;
577 const glop::RowIndex num_rows = linear_program_.num_constraints();
578 for (glop::RowIndex row(0); row < num_rows; ++row) {
579 if (linear_program_.constraint_lower_bounds()[row] >
580 linear_program_.constraint_upper_bounds()[row]) {
581 inverted_rows.push_back(row);
582 }
583 }
584
585 // Convert column/row indices into MathOpt ids. We avoid calling the expensive
586 // IndexToId() when not necessary.
587 InvertedBounds inverted_bounds;
588 if (!inverted_columns.empty()) {
589 const glop::StrictITIVector<glop::ColIndex, int64_t> ids =
590 IndexToId(variables_);
591 CHECK_EQ(ids.size(), num_cols);
592 inverted_bounds.variables.reserve(inverted_columns.size());
593 for (const glop::ColIndex col : inverted_columns) {
594 inverted_bounds.variables.push_back(ids[col]);
595 }
596 }
597 if (!inverted_rows.empty()) {
598 const glop::StrictITIVector<glop::RowIndex, int64_t> ids =
599 IndexToId(linear_constraints_);
600 CHECK_EQ(ids.size(), num_rows);
601 inverted_bounds.linear_constraints.reserve(inverted_rows.size());
602 for (const glop::RowIndex row : inverted_rows) {
603 inverted_bounds.linear_constraints.push_back(ids[row]);
604 }
605 }
606
607 return inverted_bounds;
608}
609
610void GlopSolver::FillSolution(const glop::ProblemStatus status,
611 const ModelSolveParametersProto& model_parameters,
612 SolveResultProto& solve_result) {
613 // Meaningful solutions are available if optimality is proven in
614 // preprocessing or after 1 simplex iteration.
615 // TODO(b/195295177): Discuss what to do with glop::ProblemStatus::IMPRECISE
616 // looks like it may be set also when rays are imprecise.
617 const bool phase_I_solution_available =
618 (status == glop::ProblemStatus::INIT) &&
619 (lp_solver_.GetNumberOfSimplexIterations() > 0);
620 if (status != glop::ProblemStatus::OPTIMAL &&
621 status != glop::ProblemStatus::PRIMAL_FEASIBLE &&
622 status != glop::ProblemStatus::DUAL_FEASIBLE &&
623 status != glop::ProblemStatus::PRIMAL_UNBOUNDED &&
624 status != glop::ProblemStatus::DUAL_UNBOUNDED &&
625 !phase_I_solution_available) {
626 return;
627 }
628 auto sorted_variables = GetSortedIs(variables_);
629 auto sorted_constraints = GetSortedIs(linear_constraints_);
630 SolutionProto* const solution = solve_result.add_solutions();
631 BasisProto* const basis = solution->mutable_basis();
632 PrimalSolutionProto* const primal_solution =
633 solution->mutable_primal_solution();
634 DualSolutionProto* const dual_solution = solution->mutable_dual_solution();
635
636 // Fill in feasibility statuses
637 // Note: if we reach here and status != OPTIMAL, then at least 1 simplex
638 // iteration has been executed.
639 if (status == glop::ProblemStatus::OPTIMAL) {
640 primal_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
641 basis->set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
642 dual_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
643 } else if (status == glop::ProblemStatus::PRIMAL_FEASIBLE) {
644 // Solve reached phase II of primal simplex and current basis is not
645 // optimal. Hence basis is primal feasible, but cannot be dual feasible.
646 // Dual solution could still be feasible.
647 primal_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
648 dual_solution->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
649 basis->set_basic_dual_feasibility(SOLUTION_STATUS_INFEASIBLE);
650 } else if (status == glop::ProblemStatus::DUAL_FEASIBLE) {
651 // Solve reached phase II of dual simplex and current basis is not optimal.
652 // Hence basis is dual feasible, but cannot be primal feasible. In addition,
653 // glop applies dual feasibility correction in dual simplex so feasibility
654 // of the dual solution matches dual feasibility of the basis.
655 // TODO(b/195295177): confirm with fdid
656 primal_solution->set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
657 dual_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
658 basis->set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
659 } else { // status == INIT
660 // Phase I of primal or dual simplex ran for at least one iteration
661 if (lp_solver_.GetParameters().use_dual_simplex()) {
662 // Phase I did not finish so basis is not dual feasible. In addition,
663 // glop applies dual feasibility correction so feasibility of the dual
664 // solution matches dual feasibility of the basis.
665 primal_solution->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
666 dual_solution->set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
667 basis->set_basic_dual_feasibility(SOLUTION_STATUS_INFEASIBLE);
668 } else {
669 // Phase I did not finish so basis is not primal feasible.
670 primal_solution->set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
671 dual_solution->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
672 basis->set_basic_dual_feasibility(SOLUTION_STATUS_UNDETERMINED);
673 }
674 }
675
676 // Fill in objective values
677 primal_solution->set_objective_value(lp_solver_.GetObjectiveValue());
678 if (basis->basic_dual_feasibility() == SOLUTION_STATUS_FEASIBLE) {
679 // Primal and dual objectives are the same for a dual feasible basis.
680 dual_solution->set_objective_value(primal_solution->objective_value());
681 }
682
683 // Fill solution and basis
684 *basis->mutable_constraint_status() = *basis->mutable_variable_status() =
685 FillSparseBasisStatusVector(sorted_variables, variables_,
686 lp_solver_.variable_statuses());
687 *basis->mutable_constraint_status() =
688 FillSparseBasisStatusVector(sorted_constraints, linear_constraints_,
689 lp_solver_.constraint_statuses());
690
691 *primal_solution->mutable_variable_values() = FillSparseDoubleVector(
692 sorted_variables, variables_, lp_solver_.variable_values(),
693 model_parameters.variable_values_filter());
694
695 *dual_solution->mutable_dual_values() = FillSparseDoubleVector(
696 sorted_constraints, linear_constraints_, lp_solver_.dual_values(),
697 model_parameters.dual_values_filter());
698 *dual_solution->mutable_reduced_costs() = FillSparseDoubleVector(
699 sorted_variables, variables_, lp_solver_.reduced_costs(),
700 model_parameters.reduced_costs_filter());
701
702 if (!lp_solver_.primal_ray().empty()) {
703 PrimalRayProto* const primal_ray = solve_result.add_primal_rays();
704
705 *primal_ray->mutable_variable_values() = FillSparseDoubleVector(
706 sorted_variables, variables_, lp_solver_.primal_ray(),
707 model_parameters.variable_values_filter());
708 }
709 if (!lp_solver_.constraints_dual_ray().empty() &&
710 !lp_solver_.variable_bounds_dual_ray().empty()) {
711 DualRayProto* const dual_ray = solve_result.add_dual_rays();
712 *dual_ray->mutable_dual_values() =
713 FillSparseDoubleVector(sorted_constraints, linear_constraints_,
714 lp_solver_.constraints_dual_ray(),
715 model_parameters.dual_values_filter());
716 *dual_ray->mutable_reduced_costs() = FillSparseDoubleVector(
717 sorted_variables, variables_, lp_solver_.variable_bounds_dual_ray(),
718 model_parameters.reduced_costs_filter());
719 }
720}
721
722absl::Status GlopSolver::FillSolveStats(const absl::Duration solve_time,
723 SolveStatsProto& solve_stats) {
724 // Fill remaining stats
725 solve_stats.set_simplex_iterations(lp_solver_.GetNumberOfSimplexIterations());
727 solve_time, solve_stats.mutable_solve_time()));
728
729 return absl::OkStatus();
730}
731
732absl::StatusOr<SolveResultProto> GlopSolver::MakeSolveResult(
733 const glop::ProblemStatus status,
734 const ModelSolveParametersProto& model_parameters,
735 const SolveInterrupter* absl_nullable const interrupter,
736 const absl::Duration solve_time) {
737 SolveResultProto solve_result;
738 ASSIGN_OR_RETURN(*solve_result.mutable_termination(),
739 BuildTermination(status, interrupter,
740 linear_program_.IsMaximizationProblem(),
741 lp_solver_.GetObjectiveValue()));
742 FillSolution(status, model_parameters, solve_result);
744 FillSolveStats(solve_time, *solve_result.mutable_solve_stats()));
745 return solve_result;
746}
747
748void GlopSolver::SetGlopBasis(const BasisProto& basis) {
749 glop::VariableStatusRow variable_statuses(linear_program_.num_variables());
750 for (const auto [id, value] : MakeView(basis.variable_status())) {
751 variable_statuses[variables_.at(id)] =
752 ToGlopBasisStatus<glop::VariableStatus>(
753 static_cast<BasisStatusProto>(value));
754 }
755 glop::ConstraintStatusColumn constraint_statuses(
756 linear_program_.num_constraints());
757 for (const auto [id, value] : MakeView(basis.constraint_status())) {
758 constraint_statuses[linear_constraints_.at(id)] =
759 ToGlopBasisStatus<glop::ConstraintStatus>(
760 static_cast<BasisStatusProto>(value));
761 }
762 lp_solver_.SetInitialBasis(variable_statuses, constraint_statuses);
763}
764
765absl::StatusOr<SolveResultProto> GlopSolver::Solve(
766 const SolveParametersProto& parameters,
767 const ModelSolveParametersProto& model_parameters,
768 const MessageCallback message_cb,
769 const CallbackRegistrationProto& callback_registration, const Callback,
770 const SolveInterrupter* absl_nullable const interrupter) {
772 model_parameters, kGlopSupportedStructures, "Glop"));
774 /*supported_events=*/{}));
775
776 const absl::Time start = absl::Now();
778 const glop::GlopParameters glop_parameters,
780 parameters,
781 /*setting_initial_basis=*/model_parameters.has_initial_basis(),
782 /*has_message_callback=*/message_cb != nullptr,
783 linear_program_.IsMaximizationProblem()));
784 lp_solver_.SetParameters(glop_parameters);
785
786 if (model_parameters.has_initial_basis()) {
787 SetGlopBasis(model_parameters.initial_basis());
788 }
789
790 std::atomic<bool> interrupt_solve = false;
791 const std::unique_ptr<TimeLimit> time_limit =
792 TimeLimit::FromParameters(lp_solver_.GetParameters());
793 time_limit->RegisterExternalBooleanAsLimit(&interrupt_solve);
794
795 const ScopedSolveInterrupterCallback scoped_interrupt_cb(interrupter, [&]() {
796 CHECK_NE(interrupter, nullptr);
797 interrupt_solve = true;
798 });
799
800 if (message_cb != nullptr) {
801 // Please note that the logging is enabled in MergeSolveParameters() where
802 // we also disable logging to stdout. We can't modify the SolverLogger here
803 // since the values are overwritten from the parameters at the beginning of
804 // the solve.
805 //
806 // Here we test that there are no other callbacks since we will clear them
807 // all in the cleanup below.
808 CHECK_EQ(lp_solver_.GetSolverLogger().NumInfoLoggingCallbacks(), 0);
809 lp_solver_.GetSolverLogger().AddInfoLoggingCallback(
810 [&](absl::string_view message) {
811 message_cb(absl::StrSplit(message, '\n'));
812 });
813 }
814 const auto message_cb_cleanup = absl::MakeCleanup([&]() {
815 if (message_cb != nullptr) {
816 // Check that no other callbacks have been added to the logger.
817 CHECK_EQ(lp_solver_.GetSolverLogger().NumInfoLoggingCallbacks(), 1);
818 lp_solver_.GetSolverLogger().ClearInfoLoggingCallbacks();
819 }
820 });
821
822 // Glop returns an error when bounds are inverted and does not list the
823 // offending variables/constraints. Here we want to return a more detailed
824 // status.
825 RETURN_IF_ERROR(ListInvertedBounds().ToStatus());
826
827 const glop::ProblemStatus status =
828 lp_solver_.SolveWithTimeLimit(linear_program_, time_limit.get());
829 const absl::Duration solve_time = absl::Now() - start;
830 return MakeSolveResult(status, model_parameters, interrupter, solve_time);
831}
832
833absl::StatusOr<std::unique_ptr<SolverInterface>> GlopSolver::New(
834 const ModelProto& model, const InitArgs&) {
835 RETURN_IF_ERROR(ModelIsSupported(model, kGlopSupportedStructures, "Glop"));
836 auto solver = absl::WrapUnique(new GlopSolver);
837 // By default Glop CHECKs that bounds are always consistent (lb < ub); thus it
838 // would fail if the initial model or later updates temporarily set inverted
839 // bounds.
840 solver->linear_program_.SetDcheckBounds(false);
841
842 solver->linear_program_.SetName(model.name());
843 solver->linear_program_.SetMaximizationProblem(model.objective().maximize());
844 solver->linear_program_.SetObjectiveOffset(model.objective().offset());
845
846 solver->AddVariables(model.variables());
847 solver->SetOrUpdateObjectiveCoefficients(
849
850 solver->AddLinearConstraints(model.linear_constraints());
851 solver->SetOrUpdateConstraintMatrix(model.linear_constraint_matrix());
852 solver->linear_program_.CleanUp();
853 return solver;
854}
855
856absl::StatusOr<bool> GlopSolver::Update(const ModelUpdateProto& model_update) {
857 if (!UpdateIsSupported(model_update, kGlopSupportedStructures)) {
858 return false;
859 }
860
861 if (model_update.objective_updates().has_direction_update()) {
862 linear_program_.SetMaximizationProblem(
863 model_update.objective_updates().direction_update());
864 }
865 if (model_update.objective_updates().has_offset_update()) {
866 linear_program_.SetObjectiveOffset(
867 model_update.objective_updates().offset_update());
868 }
869
870 DeleteVariables(model_update.deleted_variable_ids());
871 AddVariables(model_update.new_variables());
872
873 SetOrUpdateObjectiveCoefficients(
874 model_update.objective_updates().linear_coefficients());
875 UpdateVariableBounds(model_update.variable_updates());
876
877 DeleteLinearConstraints(model_update.deleted_linear_constraint_ids());
878 AddLinearConstraints(model_update.new_linear_constraints());
879 UpdateLinearConstraintBounds(model_update.linear_constraint_updates());
880
881 SetOrUpdateConstraintMatrix(model_update.linear_constraint_matrix_updates());
882
883 linear_program_.CleanUp();
884
885 return true;
886}
887
888absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
891 const SolveInterrupter* absl_nullable) {
892 return absl::UnimplementedError(
893 "GLOP does not implement a method to compute an infeasible subsystem");
894}
895
897
898} // namespace math_opt
899} // namespace operations_research
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
static std::unique_ptr< TimeLimit > FromParameters(const Parameters &parameters)
Definition time_limit.h:143
static constexpr ScalingAlgorithm EQUILIBRATION
void set_scaling_method(::operations_research::glop::GlopParameters_ScalingAlgorithm value)
void set_max_number_of_iterations(::int64_t value)
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto &parameters, MessageCallback message_cb, const SolveInterrupter *absl_nullable interrupter) override
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto &parameters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *absl_nullable interrupter) override
static absl::StatusOr< glop::GlopParameters > MergeSolveParameters(const SolveParametersProto &solve_parameters, bool setting_initial_basis, bool has_message_callback, bool is_maximization)
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
const ::operations_research::math_opt::VariablesProto & variables() const
Definition model.pb.h:4464
const ::operations_research::math_opt::LinearConstraintsProto & linear_constraints() const
Definition model.pb.h:4694
const ::std::string & name() const
Definition model.pb.h:4389
const ::operations_research::math_opt::ObjectiveProto & objective() const
Definition model.pb.h:4563
const ::operations_research::math_opt::SparseDoubleMatrixProto & linear_constraint_matrix() const
Definition model.pb.h:4787
const ::operations_research::math_opt::BasisProto & initial_basis() const
::int64_t deleted_linear_constraint_ids(int index) const
const ::operations_research::math_opt::SparseDoubleMatrixProto & linear_constraint_matrix_updates() const
const ::operations_research::math_opt::LinearConstraintsProto & new_linear_constraints() const
const ::operations_research::math_opt::VariableUpdatesProto & variable_updates() const
const ::operations_research::math_opt::ObjectiveUpdatesProto & objective_updates() const
const ::operations_research::math_opt::LinearConstraintUpdatesProto & linear_constraint_updates() const
const ::operations_research::math_opt::VariablesProto & new_variables() const
const ::operations_research::math_opt::SparseDoubleVectorProto & linear_coefficients() const
Definition model.pb.h:2911
const ::operations_research::math_opt::SparseDoubleVectorProto & linear_coefficients() const
::operations_research::math_opt::EmphasisProto presolve() const
const ::operations_research::glop::GlopParameters & glop() const
::operations_research::math_opt::EmphasisProto scaling() const
::operations_research::math_opt::EmphasisProto heuristics() const
::operations_research::math_opt::LPAlgorithmProto lp_algorithm() const
const ::google::protobuf::Duration & time_limit() const
::operations_research::math_opt::EmphasisProto cuts() const
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
void add_values(::operations_research::math_opt::BasisStatusProto value)
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition map_util.h:159
std::string ValidateParameters(const GlopParameters &params)
std::string GetProblemStatusString(ProblemStatus problem_status)
Definition lp_types.cc:23
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
SparseDoubleVectorProto FillSparseDoubleVector(absl::Span< const int64_t > ids_in_order, const absl::flat_hash_map< int64_t, IndexType > &id_map, const glop::StrictITIVector< IndexType, glop::Fractional > &values, const SparseVectorFilterProto &filter)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
int NumMatrixNonzeros(const SparseDoubleMatrixProto &matrix)
void UpdateIdIndexMap(glop::StrictITIVector< IndexType, bool > indices_to_delete, IndexType num_indices, absl::flat_hash_map< int64_t, IndexType > &id_index_map)
int NumVariables(const VariablesProto &variables)
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
TerminationProto InfeasibleOrUnboundedTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
bool UpdateIsSupported(const ModelUpdateProto &update, const SupportedProblemStructures &support_menu)
BasisStatusProto FromGlopBasisStatus(const ValueType glop_basis_status)
ValueType ToGlopBasisStatus(const BasisStatusProto basis_status)
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)
glop::StrictITIVector< T, int64_t > IndexToId(const absl::flat_hash_map< int64_t, T > &id_map)
TerminationProto NoSolutionFoundTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_dual_objective, const absl::string_view detail)
std::vector< int64_t > GetSortedIs(const absl::flat_hash_map< int64_t, T > &id_map)
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)
int NumConstraints(const LinearConstraintsProto &linear_constraints)
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)
SparseBasisStatusVector FillSparseBasisStatusVector(absl::Span< const int64_t > ids_in_order, const absl::flat_hash_map< int64_t, IndexType > &id_map, const glop::StrictITIVector< IndexType, ValueType > &values)
OR-Tools root namespace.
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
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition protoutil.h:42
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition protoutil.h:27
StatusBuilder InvalidArgumentErrorBuilder()
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)