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