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