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