Google OR-Tools v9.9
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
math_opt_proto_utils.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 <cmath>
18#include <cstdint>
19#include <functional>
20#include <limits>
21#include <optional>
22#include <string>
23
24#include "absl/container/flat_hash_set.h"
25#include "absl/log/check.h"
26#include "absl/status/status.h"
27#include "absl/strings/string_view.h"
30#include "ortools/math_opt/callback.pb.h"
32#include "ortools/math_opt/model.pb.h"
33#include "ortools/math_opt/model_parameters.pb.h"
34#include "ortools/math_opt/model_update.pb.h"
35#include "ortools/math_opt/result.pb.h"
36#include "ortools/math_opt/solution.pb.h"
37#include "ortools/math_opt/sparse_containers.pb.h"
38
39namespace operations_research {
40namespace math_opt {
41namespace {
42constexpr double kInf = std::numeric_limits<double>::infinity();
43} // namespace
44
45ObjectiveBoundsProto GetObjectiveBounds(const SolveResultProto& solve_result) {
46 if (solve_result.termination().has_objective_bounds()) {
47 return solve_result.termination().objective_bounds();
48 }
49 ObjectiveBoundsProto objective_bounds;
50 objective_bounds.set_primal_bound(
51 solve_result.solve_stats().best_primal_bound());
52 objective_bounds.set_dual_bound(solve_result.solve_stats().best_dual_bound());
53 return objective_bounds;
54}
55
56ProblemStatusProto GetProblemStatus(const SolveResultProto& solve_result) {
57 if (solve_result.termination().has_problem_status()) {
58 return solve_result.termination().problem_status();
59 }
60 ProblemStatusProto problem_status;
61 problem_status.set_primal_status(
62 solve_result.solve_stats().problem_status().primal_status());
63 problem_status.set_dual_status(
64 solve_result.solve_stats().problem_status().dual_status());
65 problem_status.set_primal_or_dual_infeasible(
66 solve_result.solve_stats().problem_status().primal_or_dual_infeasible());
67 return problem_status;
68}
69
70void RemoveSparseDoubleVectorZeros(SparseDoubleVectorProto& sparse_vector) {
71 CHECK_EQ(sparse_vector.ids_size(), sparse_vector.values_size());
72 // Keep track of the next index that has not yet been used for a non zero
73 // value.
74 int next = 0;
75 for (const auto [id, value] : MakeView(sparse_vector)) {
76 // Se use `!(== 0.0)` here so that we keep NaN values for which both `v ==
77 // 0` and `v != 0` returns false.
78 if (!(value == 0.0)) {
79 sparse_vector.set_ids(next, id);
80 sparse_vector.set_values(next, value);
81 ++next;
82 }
83 }
84 // At the end of the iteration, `next` contains the index of the first unused
85 // index. This means it contains the number of used elements.
86 sparse_vector.mutable_ids()->Truncate(next);
87 sparse_vector.mutable_values()->Truncate(next);
88}
89
91 const SparseVectorFilterProto& filter)
92 : filter_(filter) {
93 // We only do this test in non-optimized builds.
94 if (DEBUG_MODE && filter_.filter_by_ids()) {
95 // Checks that input filtered_ids are strictly increasing.
96 // That is: for all i, ids(i) < ids(i+1).
97 // Hence here we fail if there exists i such that ids(i) >= ids(i+1).
98 const auto& ids = filter_.filtered_ids();
99 CHECK(std::adjacent_find(ids.begin(), ids.end(),
100 std::greater_equal<int64_t>()) == ids.end())
101 << "The input filter.filtered_ids must be strictly increasing.";
102 }
103}
104
105SparseDoubleVectorProto FilterSparseVector(
106 const SparseDoubleVectorProto& input,
107 const SparseVectorFilterProto& filter) {
108 SparseDoubleVectorProto result;
109 SparseVectorFilterPredicate predicate(filter);
110 for (const auto [id, val] : MakeView(input)) {
111 if (predicate.AcceptsAndUpdate(id, val)) {
112 result.add_ids(id);
113 result.add_values(val);
114 }
115 }
116 return result;
117}
118
119void ApplyAllFilters(const ModelSolveParametersProto& model_solve_params,
120 SolutionProto& solution) {
121 if (model_solve_params.has_variable_values_filter() &&
122 solution.has_primal_solution()) {
123 *solution.mutable_primal_solution()->mutable_variable_values() =
124 FilterSparseVector(solution.primal_solution().variable_values(),
125 model_solve_params.variable_values_filter());
126 }
127 if (model_solve_params.has_dual_values_filter() &&
128 solution.has_dual_solution()) {
129 *solution.mutable_dual_solution()->mutable_dual_values() =
130 FilterSparseVector(solution.dual_solution().dual_values(),
131 model_solve_params.dual_values_filter());
132 }
133 if (model_solve_params.has_reduced_costs_filter() &&
134 solution.has_dual_solution()) {
135 *solution.mutable_dual_solution()->mutable_reduced_costs() =
136 FilterSparseVector(solution.dual_solution().reduced_costs(),
137 model_solve_params.reduced_costs_filter());
138 }
139}
140
141absl::flat_hash_set<CallbackEventProto> EventSet(
142 const CallbackRegistrationProto& callback_registration) {
143 // Here we don't use for-range loop since for repeated enum fields, the type
144 // used in C++ is RepeatedField<int>. Using the generated getter instead
145 // guarantees type safety.
146 absl::flat_hash_set<CallbackEventProto> events;
147 for (int i = 0; i < callback_registration.request_registration_size(); ++i) {
148 events.emplace(callback_registration.request_registration(i));
149 }
150 return events;
151}
152
153TerminationProto TerminateForLimit(const LimitProto limit, const bool feasible,
154 const absl::string_view detail) {
155 TerminationProto result;
156 if (feasible) {
157 result.set_reason(TERMINATION_REASON_FEASIBLE);
158 } else {
159 result.set_reason(TERMINATION_REASON_NO_SOLUTION_FOUND);
160 }
161 result.set_limit(limit);
162 if (!detail.empty()) {
163 result.set_detail(std::string(detail));
164 }
165 return result;
166}
167
168TerminationProto FeasibleTermination(const LimitProto limit,
169 const absl::string_view detail) {
170 return TerminateForLimit(limit, /*feasible=*/true, detail);
171}
172
173TerminationProto NoSolutionFoundTermination(const LimitProto limit,
174 const absl::string_view detail) {
175 return TerminateForLimit(limit, /*feasible=*/false, detail);
176}
177
178TerminationProto TerminateForReason(const TerminationReasonProto reason,
179 const absl::string_view detail) {
180 TerminationProto result;
181 result.set_reason(reason);
182 if (!detail.empty()) {
183 result.set_detail(std::string(detail));
184 }
185 return result;
186}
187
188ObjectiveBoundsProto MakeTrivialBounds(const bool is_maximize) {
189 ObjectiveBoundsProto bounds;
190 bounds.set_primal_bound(is_maximize ? -kInf : +kInf);
191 bounds.set_dual_bound(is_maximize ? +kInf : -kInf);
192 return bounds;
193}
194
195namespace {
196ObjectiveBoundsProto MakeUnboundedBounds(const bool is_maximize) {
197 ObjectiveBoundsProto bounds;
198 bounds.set_primal_bound(is_maximize ? +kInf : -kInf);
199 bounds.set_dual_bound(bounds.primal_bound());
200 return bounds;
201}
202} // namespace
203
204TerminationProto TerminateForReason(const bool is_maximize,
205 const TerminationReasonProto reason,
206 const absl::string_view detail) {
207 TerminationProto result;
208 result.set_reason(reason);
209 result.mutable_problem_status()->set_primal_status(
210 FEASIBILITY_STATUS_UNDETERMINED);
211 result.mutable_problem_status()->set_dual_status(
212 FEASIBILITY_STATUS_UNDETERMINED);
213 *result.mutable_objective_bounds() = MakeTrivialBounds(is_maximize);
214 if (!detail.empty()) {
215 result.set_detail(std::string(detail));
216 }
217 return result;
218}
219
220TerminationProto OptimalTerminationProto(const double finite_primal_objective,
221 const double dual_objective,
222 const absl::string_view detail) {
223 TerminationProto result;
224 result.set_reason(TERMINATION_REASON_OPTIMAL);
225 result.mutable_objective_bounds()->set_primal_bound(finite_primal_objective);
226 result.mutable_objective_bounds()->set_dual_bound(dual_objective);
227 result.mutable_problem_status()->set_primal_status(
228 FEASIBILITY_STATUS_FEASIBLE);
229 result.mutable_problem_status()->set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
230 if (!detail.empty()) {
231 result.set_detail(std::string(detail));
232 }
233 return result;
234}
235
236TerminationProto UnboundedTerminationProto(const bool is_maximize,
237 const absl::string_view detail) {
238 TerminationProto result;
239 result.set_reason(TERMINATION_REASON_UNBOUNDED);
240 result.mutable_problem_status()->set_primal_status(
241 FEASIBILITY_STATUS_FEASIBLE);
242 result.mutable_problem_status()->set_dual_status(
243 FEASIBILITY_STATUS_INFEASIBLE);
244 *result.mutable_objective_bounds() = MakeUnboundedBounds(is_maximize);
245 if (!detail.empty()) {
246 result.set_detail(std::string(detail));
247 }
248 return result;
249}
250
252 bool is_maximize, const FeasibilityStatusProto dual_feasibility_status,
253 const absl::string_view detail) {
254 TerminationProto result;
255 result.set_reason(TERMINATION_REASON_INFEASIBLE);
256 result.mutable_problem_status()->set_primal_status(
257 FEASIBILITY_STATUS_INFEASIBLE);
258 result.mutable_problem_status()->set_dual_status(dual_feasibility_status);
259 *result.mutable_objective_bounds() = MakeTrivialBounds(is_maximize);
260 if (dual_feasibility_status == FEASIBILITY_STATUS_FEASIBLE) {
261 result.mutable_objective_bounds()->set_dual_bound(
262 result.objective_bounds().primal_bound());
263 }
264 if (!detail.empty()) {
265 result.set_detail(std::string(detail));
266 }
267 return result;
268}
269
270TerminationProto LimitTerminationProto(
271 const bool is_maximize, const LimitProto limit,
272 const std::optional<double> optional_finite_primal_objective,
273 const std::optional<double> optional_dual_objective,
274 const absl::string_view detail) {
275 if (optional_finite_primal_objective.has_value()) {
276 return FeasibleTerminationProto(is_maximize, limit,
277 *optional_finite_primal_objective,
278 optional_dual_objective, detail);
279 }
280 return NoSolutionFoundTerminationProto(is_maximize, limit,
281 optional_dual_objective, detail);
282}
283
284TerminationProto LimitTerminationProto(
285 LimitProto limit, const double primal_objective,
286 const double dual_objective, const bool claim_dual_feasible_solution_exists,
287 const absl::string_view detail) {
288 TerminationProto result;
289 if (std::isfinite(primal_objective)) {
290 result.set_reason(TERMINATION_REASON_FEASIBLE);
291 result.mutable_problem_status()->set_primal_status(
292 FEASIBILITY_STATUS_FEASIBLE);
293 } else {
294 result.set_reason(TERMINATION_REASON_NO_SOLUTION_FOUND);
295 result.mutable_problem_status()->set_primal_status(
296 FEASIBILITY_STATUS_UNDETERMINED);
297 }
298 if (claim_dual_feasible_solution_exists) {
299 result.mutable_problem_status()->set_dual_status(
300 FEASIBILITY_STATUS_FEASIBLE);
301 } else {
302 result.mutable_problem_status()->set_dual_status(
303 FEASIBILITY_STATUS_UNDETERMINED);
304 }
305 result.mutable_objective_bounds()->set_primal_bound(primal_objective);
306 result.mutable_objective_bounds()->set_dual_bound(dual_objective);
307 result.set_limit(limit);
308 if (!detail.empty()) {
309 result.set_detail(std::string(detail));
310 }
311 return result;
312}
313
314TerminationProto CutoffTerminationProto(bool is_maximize,
315 const absl::string_view detail) {
317 is_maximize, LIMIT_CUTOFF, /*optional_dual_objective=*/std::nullopt,
318 detail);
319}
320
322 const bool is_maximize, const LimitProto limit,
323 const std::optional<double> optional_dual_objective,
324 const absl::string_view detail) {
325 TerminationProto result;
326 result.set_reason(TERMINATION_REASON_NO_SOLUTION_FOUND);
327 result.mutable_problem_status()->set_primal_status(
328 FEASIBILITY_STATUS_UNDETERMINED);
329 result.mutable_problem_status()->set_dual_status(
330 FEASIBILITY_STATUS_UNDETERMINED);
331 *result.mutable_objective_bounds() = MakeTrivialBounds(is_maximize);
332 if (optional_dual_objective.has_value()) {
333 result.mutable_objective_bounds()->set_dual_bound(*optional_dual_objective);
334 result.mutable_problem_status()->set_dual_status(
335 FEASIBILITY_STATUS_FEASIBLE);
336 }
337 result.set_limit(limit);
338 if (!detail.empty()) {
339 result.set_detail(std::string(detail));
340 }
341 return result;
342}
343
345 const bool is_maximize, const LimitProto limit,
346 const double primal_objective,
347 const std::optional<double> optional_dual_objective,
348 const absl::string_view detail) {
349 TerminationProto result;
350 result.set_reason(TERMINATION_REASON_FEASIBLE);
351 result.mutable_problem_status()->set_primal_status(
352 FEASIBILITY_STATUS_FEASIBLE);
353 result.mutable_problem_status()->set_dual_status(
354 FEASIBILITY_STATUS_UNDETERMINED);
355 *result.mutable_objective_bounds() = MakeTrivialBounds(is_maximize);
356 result.mutable_objective_bounds()->set_primal_bound(primal_objective);
357 if (optional_dual_objective.has_value()) {
358 result.mutable_objective_bounds()->set_dual_bound(*optional_dual_objective);
359 result.mutable_problem_status()->set_dual_status(
360 FEASIBILITY_STATUS_FEASIBLE);
361 }
362 result.set_limit(limit);
363 if (!detail.empty()) {
364 result.set_detail(std::string(detail));
365 }
366 return result;
367}
368
370 bool is_maximize, const FeasibilityStatusProto dual_feasibility_status,
371 const absl::string_view detail) {
372 TerminationProto result;
373 result.set_reason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED);
374 result.mutable_problem_status()->set_primal_status(
375 FEASIBILITY_STATUS_UNDETERMINED);
376 result.mutable_problem_status()->set_dual_status(dual_feasibility_status);
377 if (dual_feasibility_status == FEASIBILITY_STATUS_UNDETERMINED) {
378 result.mutable_problem_status()->set_primal_or_dual_infeasible(true);
379 }
380 *result.mutable_objective_bounds() = MakeTrivialBounds(is_maximize);
381 if (!detail.empty()) {
382 result.set_detail(std::string(detail));
383 }
384 return result;
385}
386
387absl::Status ModelIsSupported(const ModelProto& model,
388 const SupportedProblemStructures& support_menu,
389 const absl::string_view solver_name) {
390 const auto error_status = [solver_name](
391 const absl::string_view structure,
392 const SupportType support) -> absl::Status {
393 switch (support) {
396 << solver_name << " does not support " << structure;
399 << "MathOpt does not currently support " << solver_name
400 << " models with " << structure;
402 LOG(FATAL) << "Unexpected call with `kSupported`";
403 }
404 };
405 if (const SupportType support = support_menu.integer_variables;
406 support != SupportType::kSupported) {
407 for (const bool is_integer : model.variables().integers()) {
408 if (is_integer) {
409 return error_status("integer variables", support);
410 }
411 }
412 }
413 if (const SupportType support = support_menu.multi_objectives;
414 support != SupportType::kSupported) {
415 if (!model.auxiliary_objectives().empty()) {
416 return error_status("multiple objectives", support);
417 }
418 }
419 if (const SupportType support = support_menu.quadratic_objectives;
420 support != SupportType::kSupported) {
421 if (!model.objective().quadratic_coefficients().row_ids().empty()) {
422 return error_status("quadratic objectives", support);
423 }
424 for (const auto& [_, objective] : model.auxiliary_objectives()) {
425 if (!objective.quadratic_coefficients().row_ids().empty()) {
426 return error_status("quadratic objectives", support);
427 }
428 }
429 }
430 if (const SupportType support = support_menu.quadratic_constraints;
431 support != SupportType::kSupported) {
432 if (!model.quadratic_constraints().empty()) {
433 return error_status("quadratic constraints", support);
434 }
435 }
436 if (const SupportType support = support_menu.second_order_cone_constraints;
437 support != SupportType::kSupported) {
438 if (!model.second_order_cone_constraints().empty()) {
439 return error_status("second-order cone constraints", support);
440 }
441 }
442 if (const SupportType support = support_menu.sos1_constraints;
443 support != SupportType::kSupported) {
444 if (!model.sos1_constraints().empty()) {
445 return error_status("sos1 constraints", support);
446 }
447 }
448 if (const SupportType support = support_menu.sos2_constraints;
449 support != SupportType::kSupported) {
450 if (!model.sos2_constraints().empty()) {
451 return error_status("sos2 constraints", support);
452 }
453 }
454 if (const SupportType support = support_menu.indicator_constraints;
455 support != SupportType::kSupported) {
456 if (!model.indicator_constraints().empty()) {
457 return error_status("indicator constraints", support);
458 }
459 }
460 return absl::OkStatus();
461}
462
463bool UpdateIsSupported(const ModelUpdateProto& update,
464 const SupportedProblemStructures& support_menu) {
465 if (support_menu.integer_variables != SupportType::kSupported) {
466 for (const bool is_integer :
467 update.variable_updates().integers().values()) {
468 if (is_integer) {
469 return false;
470 }
471 }
472 for (const bool is_integer : update.new_variables().integers()) {
473 if (is_integer) {
474 return false;
475 }
476 }
477 }
478 if (support_menu.multi_objectives != SupportType::kSupported) {
479 if (!update.auxiliary_objectives_updates()
480 .deleted_objective_ids()
481 .empty() ||
482 !update.auxiliary_objectives_updates().new_objectives().empty() ||
483 !update.auxiliary_objectives_updates().objective_updates().empty()) {
484 return false;
485 }
486 }
487 if (support_menu.quadratic_objectives != SupportType::kSupported) {
488 if (!update.objective_updates()
489 .quadratic_coefficients()
490 .row_ids()
491 .empty()) {
492 return false;
493 }
494 for (const auto& [_, new_objective] :
495 update.auxiliary_objectives_updates().new_objectives()) {
496 if (!new_objective.quadratic_coefficients().row_ids().empty()) {
497 return false;
498 }
499 }
500 for (const auto& [_, objective_update] :
501 update.auxiliary_objectives_updates().objective_updates()) {
502 if (!objective_update.quadratic_coefficients().row_ids().empty()) {
503 return false;
504 }
505 }
506 }
507 // Duck-types that the proto parameter contains fields named `new_constraints`
508 // and `deleted_constraint_ids`. This is standard for "mapped" constraints.
509 const auto contains_new_or_deleted_constraints =
510 [](const auto& constraint_update) {
511 return !constraint_update.new_constraints().empty() ||
512 !constraint_update.deleted_constraint_ids().empty();
513 };
515 if (contains_new_or_deleted_constraints(
516 update.quadratic_constraint_updates())) {
517 return false;
518 }
519 }
521 if (contains_new_or_deleted_constraints(
522 update.second_order_cone_constraint_updates())) {
523 return false;
524 }
525 }
526 if (support_menu.sos1_constraints != SupportType::kSupported) {
527 if (contains_new_or_deleted_constraints(update.sos1_constraint_updates())) {
528 return false;
529 }
530 }
531 if (support_menu.sos2_constraints != SupportType::kSupported) {
532 if (contains_new_or_deleted_constraints(update.sos2_constraint_updates())) {
533 return false;
534 }
535 }
537 if (contains_new_or_deleted_constraints(
538 update.indicator_constraint_updates())) {
539 return false;
540 }
541 }
542 return true;
543}
544
546 SolveResultProto& solve_result_proto) {
547 *solve_result_proto.mutable_termination()->mutable_problem_status() =
548 GetProblemStatus(solve_result_proto);
549 *solve_result_proto.mutable_solve_stats()->mutable_problem_status() =
550 GetProblemStatus(solve_result_proto);
551 *solve_result_proto.mutable_termination()->mutable_objective_bounds() =
552 GetObjectiveBounds(solve_result_proto);
553 solve_result_proto.mutable_solve_stats()->set_best_primal_bound(
554 solve_result_proto.termination().objective_bounds().primal_bound());
555 solve_result_proto.mutable_solve_stats()->set_best_dual_bound(
556 solve_result_proto.termination().objective_bounds().dual_bound());
557}
558
559} // namespace math_opt
560} // namespace operations_research
SparseVectorFilterPredicate(const SparseVectorFilterProto &filter)
Block * next
std::unique_ptr< SharedBoundsManager > bounds
These can be nullptr depending on the options.
int64_t value
GRBmodel * model
Filter filter_
const bool DEBUG_MODE
Definition macros.h:24
double solution
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
TerminationProto FeasibleTermination(const LimitProto limit, const absl::string_view detail)
void UpgradeSolveResultProtoForStatsMigration(SolveResultProto &solve_result_proto)
TerminationProto TerminateForLimit(const LimitProto limit, const bool feasible, const absl::string_view detail)
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
Returns the callback_registration.request_registration as a set of enums.
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
TerminationProto LimitTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_finite_primal_objective, const std::optional< double > optional_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)
ProblemStatusProto GetProblemStatus(const SolveResultProto &solve_result)
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)
ObjectiveBoundsProto GetObjectiveBounds(const SolveResultProto &solve_result)
TerminationProto NoSolutionFoundTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_dual_objective, const absl::string_view detail)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
TerminationProto CutoffTerminationProto(bool is_maximize, const absl::string_view detail)
Calls NoSolutionFoundTerminationProto() with LIMIT_CUTOFF LIMIT.
TerminationProto NoSolutionFoundTermination(const LimitProto limit, const absl::string_view detail)
TerminationProto InfeasibleTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
SparseDoubleVectorProto FilterSparseVector(const SparseDoubleVectorProto &input, const SparseVectorFilterProto &filter)
TerminationProto UnboundedTerminationProto(const bool is_maximize, const absl::string_view detail)
void RemoveSparseDoubleVectorZeros(SparseDoubleVectorProto &sparse_vector)
void ApplyAllFilters(const ModelSolveParametersProto &model_solve_params, SolutionProto &solution)
ObjectiveBoundsProto MakeTrivialBounds(const bool is_maximize)
In SWIG mode, we don't want anything besides these top-level includes.
StatusBuilder UnimplementedErrorBuilder()
StatusBuilder InvalidArgumentErrorBuilder()
static int input(yyscan_t yyscanner)