Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
objective_storage.h
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
14#ifndef OR_TOOLS_MATH_OPT_STORAGE_OBJECTIVE_STORAGE_H_
15#define OR_TOOLS_MATH_OPT_STORAGE_OBJECTIVE_STORAGE_H_
16
17#include <algorithm>
18#include <cstdint>
19#include <optional>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/container/flat_hash_map.h"
25#include "absl/container/flat_hash_set.h"
26#include "absl/types/span.h"
27#include "google/protobuf/map.h"
29#include "ortools/math_opt/model.pb.h"
30#include "ortools/math_opt/model_update.pb.h"
34
36
37// In memory representation of the objective of an optimization model.
39 public:
40 // Tracks the changes to ObjectiveStorage. Advancing the checkpoint throws
41 // away tracked changes.
42 //
43 // An instance of this class is owned by each update tracker of ModelStorage.
44 struct Diff {
46 inline bool empty() const;
47 // The quadratic coefficient diff is not indexed symmetrically, so we need
48 // additional information to determine which quadratic terms are dirty.
49 void DeleteVariable(VariableId deleted_variable,
50 VariableId variable_checkpoint,
52
53 bool direction = false;
54 bool priority = false;
55 bool offset = false;
56 // Only for terms where the variable is before the variable_checkpoint
57 // and, if an auxiliary objective, the objective is before the
58 // objective_checkpoint.
59 absl::flat_hash_set<VariableId> linear_coefficients;
60
61 // For each entry, first <= second (the matrix is symmetric).
62 // Only holds entries with both variables before the variable checkpoint
63 // and, if an auxiliary objective, the objective is before the
64 // objective_checkpoint.
65 absl::flat_hash_set<std::pair<VariableId, VariableId>>
67 };
68
69 inline explicit Diff(const ObjectiveStorage& storage,
70 VariableId variable_checkpoint);
71
72 // Returns true if objective `id` is already tracked by the diff. Otherwise,
73 // it should be considered a "new" objective.
74 inline bool objective_tracked(ObjectiveId id) const;
75
76 AuxiliaryObjectiveId objective_checkpoint{0};
77 VariableId variable_checkpoint{0};
78 // No guarantees provided on which objectives have corresponding entries,
79 // or that values are not `empty()`.
80 // TODO(b/259109678): Consider storing primary objective separately (like in
81 // `ObjectiveStorage`) if hashing is a noticeable bottleneck.
82 absl::flat_hash_map<ObjectiveId, SingleObjective> objective_diffs;
83 absl::flat_hash_set<AuxiliaryObjectiveId> deleted;
84 };
85
86 inline explicit ObjectiveStorage(absl::string_view name = {});
87
88 // Adds an auxiliary objective to the model and returns its id.
89 //
90 // The returned ids begin at zero and strictly increase (in particular, if
91 // ensure_next_id_at_least() is not used, they will be consecutive). Deleted
92 // ids are NOT reused.
93 AuxiliaryObjectiveId AddAuxiliaryObjective(int64_t priority,
94 absl::string_view name);
95
96 inline bool maximize(ObjectiveId id) const;
97 inline int64_t priority(ObjectiveId id) const;
98 inline double offset(ObjectiveId id) const;
99 inline double linear_term(ObjectiveId id, VariableId v) const;
100 inline double quadratic_term(ObjectiveId id, VariableId v1,
101 VariableId v2) const;
102 inline const std::string& name(ObjectiveId id) const;
103 inline const absl::flat_hash_map<VariableId, double>& linear_terms(
104 ObjectiveId id) const;
105 inline const SparseSymmetricMatrix& quadratic_terms(ObjectiveId id) const;
106
107 template <typename DiffIter>
108 void set_maximize(ObjectiveId id, bool maximize,
109 const iterator_range<DiffIter>& diffs);
110
111 template <typename DiffIter>
112 void set_priority(ObjectiveId id, int64_t priority,
113 const iterator_range<DiffIter>& diffs);
114
115 template <typename DiffIter>
116 void set_offset(ObjectiveId id, double offset,
117 const iterator_range<DiffIter>& diffs);
118
119 template <typename DiffIter>
120 void set_linear_term(ObjectiveId id, VariableId variable, double value,
121 const iterator_range<DiffIter>& diffs);
122
123 template <typename DiffIter>
124 void set_quadratic_term(ObjectiveId id, VariableId v1, VariableId v2,
125 double val, const iterator_range<DiffIter>& diffs);
126
127 // Removes an auxiliary objective from the model.
128 //
129 // It is an error to use a deleted auxiliary objective id as input to any
130 // subsequent function calls on the model.
131 template <typename DiffIter>
132 void Delete(AuxiliaryObjectiveId id, const iterator_range<DiffIter>& diffs);
133
134 // The number of auxiliary objectives in the model.
135 //
136 // Equal to the number of auxiliary objectives created minus the number of
137 // auxiliary objectives deleted.
138 inline int64_t num_auxiliary_objectives() const;
139
140 // The returned id of the next call to AddAuxiliaryObjective.
141 //
142 // Equal to the number of auxiliary objectives created.
143 inline AuxiliaryObjectiveId next_id() const;
144
145 // Sets the next auxiliary objective id to be the maximum of next_id() and
146 // `minimum`.
147 inline void ensure_next_id_at_least(AuxiliaryObjectiveId minimum);
148
149 // Returns true if this id has been created and not yet deleted.
150 inline bool contains(AuxiliaryObjectiveId id) const;
151
152 // The AuxiliaryObjectivesIds in use (not deleted), order not defined.
153 std::vector<AuxiliaryObjectiveId> AuxiliaryObjectives() const;
154
155 // Returns a sorted vector of all existing (not deleted) auxiliary objectives
156 // in the model.
157 //
158 // Runs in O(n log(n)), where n is the number of auxiliary objectives
159 // returned.
160 std::vector<AuxiliaryObjectiveId> SortedAuxiliaryObjectives() const;
161
162 // Clears the objective function (coefficients and offset), but not the
163 // sense or priority.
164 template <typename DiffIter>
165 void Clear(ObjectiveId id, const iterator_range<DiffIter>& diffs);
166
167 // Removes all occurrences of var from the objective. Runs in O(# objectives)
168 // time (though this can potentially be improved to O(1) if the need arises).
169 template <typename DiffIter>
170 void DeleteVariable(VariableId variable,
171 const iterator_range<DiffIter>& diffs);
172
173 // Returns a proto description for the primary objective (.first) and all
174 // auxiliary objectives (.second).
175 std::pair<ObjectiveProto, google::protobuf::Map<int64_t, ObjectiveProto>>
176 Proto() const;
177
179 // Functions for working with Diff
181
182 // Returns true if there are no changes (tracked changes before the
183 // checkpoint).
184 //
185 // NOTE: when there are new variables with nonzero objective coefficient, the
186 // Diff object can be empty (and diff_is_empty will return true), but Update()
187 // can return a non-empty ObjectiveUpdatesProto. This behavior MAY CHANGE in
188 // the future (this new behavior would be more intuitive, though it is harder
189 // to implement efficiently).
190 inline bool diff_is_empty(const Diff& diff) const;
191
192 std::pair<ObjectiveUpdatesProto, AuxiliaryObjectivesUpdatesProto> Update(
193 const Diff& diff,
194 const absl::flat_hash_set<VariableId>& deleted_variables,
195 absl::Span<const VariableId> new_variables) const;
196
197 // Updates the checkpoint and clears all stored changes in diff.
198 void AdvanceCheckpointInDiff(VariableId variable_checkpoint,
199 Diff& diff) const;
200
201 private:
202 struct ObjectiveData {
203 ObjectiveProto Proto() const;
204 // Returns a proto representing the objective changes with respect to the
205 // `diff_data`. If there is no change, returns nullopt.
206 std::optional<ObjectiveUpdatesProto> Update(
207 const Diff::SingleObjective& diff_data,
208 const absl::flat_hash_set<VariableId>& deleted_variables,
209 absl::Span<const VariableId> new_variables) const;
210
211 inline void DeleteVariable(VariableId variable);
212
213 bool maximize = false;
214 int64_t priority = 0;
215 double offset = 0.0;
216 SparseCoefficientMap linear_terms;
217 SparseSymmetricMatrix quadratic_terms;
218 std::string name = "";
219 };
220
221 inline const ObjectiveData& objective(ObjectiveId id) const;
222 inline ObjectiveData& objective(ObjectiveId id);
223
224 AuxiliaryObjectiveId next_id_{0};
225 ObjectiveData primary_objective_;
226 absl::flat_hash_map<AuxiliaryObjectiveId, ObjectiveData>
227 auxiliary_objectives_;
228};
229
231// Inline function implementation
233
235 return !direction && !priority && !offset && linear_coefficients.empty() &&
237}
240 const VariableId variable_checkpoint)
241 : objective_checkpoint(storage.next_id()),
244ObjectiveStorage::ObjectiveStorage(const absl::string_view name) {
245 primary_objective_.name = std::string(name);
246}
247
249 return objective(id).maximize;
250}
251
253 return objective(id).priority;
254}
255
256double ObjectiveStorage::offset(const ObjectiveId id) const {
257 return objective(id).offset;
258}
259
261 const VariableId v) const {
262 return objective(id).linear_terms.get(v);
263}
266 const VariableId v1,
267 const VariableId v2) const {
268 return objective(id).quadratic_terms.get(v1, v2);
270
271const std::string& ObjectiveStorage::name(const ObjectiveId id) const {
272 return objective(id).name;
273}
274
275const absl::flat_hash_map<VariableId, double>& ObjectiveStorage::linear_terms(
276 const ObjectiveId id) const {
277 return objective(id).linear_terms.terms();
278}
281 const ObjectiveId id) const {
282 return objective(id).quadratic_terms;
283}
285template <typename DiffIter>
286void ObjectiveStorage::set_maximize(const ObjectiveId id, const bool maximize,
287 const iterator_range<DiffIter>& diffs) {
288 if (objective(id).maximize == maximize) {
289 return;
291 objective(id).maximize = maximize;
292 for (Diff& diff : diffs) {
293 if (diff.objective_tracked(id)) {
294 diff.objective_diffs[id].direction = true;
295 }
296 }
297}
298
299template <typename DiffIter>
301 const int64_t priority,
302 const iterator_range<DiffIter>& diffs) {
303 if (objective(id).priority == priority) {
304 return;
305 }
306 objective(id).priority = priority;
307 for (Diff& diff : diffs) {
308 if (diff.objective_tracked(id)) {
309 diff.objective_diffs[id].priority = true;
310 }
311 }
312}
313
314template <typename DiffIter>
315void ObjectiveStorage::set_offset(const ObjectiveId id, const double offset,
316 const iterator_range<DiffIter>& diffs) {
317 if (objective(id).offset == offset) {
318 return;
320 objective(id).offset = offset;
321 for (Diff& diff : diffs) {
322 if (diff.objective_tracked(id)) {
323 diff.objective_diffs[id].offset = true;
324 }
325 }
326}
327
328template <typename DiffIter>
330 const VariableId variable,
331 const double value,
332 const iterator_range<DiffIter>& diffs) {
333 if (objective(id).linear_terms.set(variable, value)) {
334 for (Diff& diff : diffs) {
335 if (diff.objective_tracked(id) && variable < diff.variable_checkpoint) {
336 diff.objective_diffs[id].linear_coefficients.insert(variable);
337 }
338 }
339 }
340}
341
342template <typename DiffIter>
344 const ObjectiveId id, const VariableId v1, const VariableId v2,
345 const double val, const iterator_range<DiffIter>& diffs) {
346 if (objective(id).quadratic_terms.set(v1, v2, val)) {
347 for (Diff& diff : diffs) {
348 if (diff.objective_tracked(id) && v1 < diff.variable_checkpoint &&
349 v2 < diff.variable_checkpoint) {
350 diff.objective_diffs[id].quadratic_coefficients.insert(
351 {std::min(v1, v2), std::max(v1, v2)});
352 }
353 }
354 }
355}
356
357template <typename DiffIter>
358void ObjectiveStorage::Delete(const AuxiliaryObjectiveId id,
359 const iterator_range<DiffIter>& diffs) {
360 CHECK(auxiliary_objectives_.contains(id));
361 for (Diff& diff : diffs) {
362 if (diff.objective_tracked(id)) {
363 diff.deleted.insert(id);
364 diff.objective_diffs.erase(id);
365 }
366 }
367 auxiliary_objectives_.erase(id);
368}
369
371 return auxiliary_objectives_.size();
372}
373
374inline AuxiliaryObjectiveId ObjectiveStorage::next_id() const {
375 return next_id_;
376}
377
379 const AuxiliaryObjectiveId minimum) {
380 next_id_ = std::max(minimum, next_id_);
381}
383bool ObjectiveStorage::contains(const AuxiliaryObjectiveId id) const {
384 return auxiliary_objectives_.contains(id);
385}
386
387template <typename DiffIter>
389 const iterator_range<DiffIter>& diffs) {
390 ObjectiveData& data = objective(id);
391 set_offset(id, 0.0, diffs);
392 for (ObjectiveStorage::Diff& diff : diffs) {
393 if (!diff.objective_tracked(id)) {
394 continue;
395 }
396 for (const auto& [var, _] : data.linear_terms.terms()) {
397 if (var < diff.variable_checkpoint) {
398 diff.objective_diffs[id].linear_coefficients.insert(var);
399 }
400 }
401 for (const auto& [v1, v2, _] : data.quadratic_terms.Terms()) {
402 if (v2 < diff.variable_checkpoint) { // v1 <= v2 is implied
403 diff.objective_diffs[id].quadratic_coefficients.insert({v1, v2});
404 }
405 }
406 }
407 data.linear_terms.clear();
408 data.quadratic_terms.Clear();
409}
410
411template <typename DiffIter>
412void ObjectiveStorage::DeleteVariable(const VariableId variable,
413 const iterator_range<DiffIter>& diffs) {
414 for (Diff& diff : diffs) {
415 for (auto& [id, obj_diff_data] : diff.objective_diffs) {
416 obj_diff_data.DeleteVariable(variable, diff.variable_checkpoint,
417 quadratic_terms(id));
418 }
419 }
420 primary_objective_.DeleteVariable(variable);
421 for (auto& [unused, aux_obj] : auxiliary_objectives_) {
422 aux_obj.DeleteVariable(variable);
423 }
424}
425
426bool ObjectiveStorage::diff_is_empty(const Diff& diff) const {
427 if (next_id_ > diff.objective_checkpoint) {
428 // There is a new auxiliary objective that needs extracting.
429 return false;
431 for (const auto& [unused, diff_data] : diff.objective_diffs) {
432 if (!diff_data.empty()) {
433 // We must apply an objective modification.
434 return false;
435 }
436 }
437 // If nonempty we need to delete some auxiliary objectives.
438 return diff.deleted.empty();
439}
440
442 // The primary objective is always present, so updates are always exported.
443 if (id == kPrimaryObjectiveId) {
444 return true;
446 return id < objective_checkpoint;
447}
448
449const ObjectiveStorage::ObjectiveData& ObjectiveStorage::objective(
450 ObjectiveId id) const {
451 if (id == kPrimaryObjectiveId) {
452 return primary_objective_;
453 }
454 const auto it = auxiliary_objectives_.find(*id);
455 CHECK(it != auxiliary_objectives_.end());
456 return it->second;
457}
458
459ObjectiveStorage::ObjectiveData& ObjectiveStorage::objective(ObjectiveId id) {
460 if (id == kPrimaryObjectiveId) {
461 return primary_objective_;
462 }
463 const auto it = auxiliary_objectives_.find(*id);
464 CHECK(it != auxiliary_objectives_.end());
465 return it->second;
466}
467
468void ObjectiveStorage::ObjectiveData::DeleteVariable(VariableId variable) {
469 linear_terms.erase(variable);
470 quadratic_terms.Delete(variable);
471}
472
473} // namespace operations_research::math_opt
474
475#endif // OR_TOOLS_MATH_OPT_STORAGE_OBJECTIVE_STORAGE_H_
In memory representation of the objective of an optimization model.
void set_linear_term(ObjectiveId id, VariableId variable, double value, const iterator_range< DiffIter > &diffs)
bool contains(AuxiliaryObjectiveId id) const
Returns true if this id has been created and not yet deleted.
const SparseSymmetricMatrix & quadratic_terms(ObjectiveId id) const
void Clear(ObjectiveId id, const iterator_range< DiffIter > &diffs)
std::pair< ObjectiveProto, google::protobuf::Map< int64_t, ObjectiveProto > > Proto() const
std::vector< AuxiliaryObjectiveId > SortedAuxiliaryObjectives() const
void set_priority(ObjectiveId id, int64_t priority, const iterator_range< DiffIter > &diffs)
const absl::flat_hash_map< VariableId, double > & linear_terms(ObjectiveId id) const
const std::string & name(ObjectiveId id) const
void set_offset(ObjectiveId id, double offset, const iterator_range< DiffIter > &diffs)
void AdvanceCheckpointInDiff(VariableId variable_checkpoint, Diff &diff) const
Updates the checkpoint and clears all stored changes in diff.
void Delete(AuxiliaryObjectiveId id, const iterator_range< DiffIter > &diffs)
std::vector< AuxiliaryObjectiveId > AuxiliaryObjectives() const
The AuxiliaryObjectivesIds in use (not deleted), order not defined.
AuxiliaryObjectiveId AddAuxiliaryObjective(int64_t priority, absl::string_view name)
double linear_term(ObjectiveId id, VariableId v) const
void set_maximize(ObjectiveId id, bool maximize, const iterator_range< DiffIter > &diffs)
void DeleteVariable(VariableId variable, const iterator_range< DiffIter > &diffs)
void ensure_next_id_at_least(AuxiliaryObjectiveId minimum)
double quadratic_term(ObjectiveId id, VariableId v1, VariableId v2) const
std::pair< ObjectiveUpdatesProto, AuxiliaryObjectivesUpdatesProto > Update(const Diff &diff, const absl::flat_hash_set< VariableId > &deleted_variables, absl::Span< const VariableId > new_variables) const
void set_quadratic_term(ObjectiveId id, VariableId v1, VariableId v2, double val, const iterator_range< DiffIter > &diffs)
void Delete(VariableId variable)
Zeros out all coefficients for this variable.
bool set(VariableId first, VariableId second, double value)
const std::string name
A name for logging purposes.
int64_t value
IntVar * var
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
constexpr ObjectiveId kPrimaryObjectiveId
std::optional< AuxiliaryObjectiveId > ObjectiveId
nullopt denotes the primary objective.
absl::flat_hash_set< std::pair< VariableId, VariableId > > quadratic_coefficients
void DeleteVariable(VariableId deleted_variable, VariableId variable_checkpoint, const SparseSymmetricMatrix &quadratic_terms)
absl::flat_hash_map< ObjectiveId, SingleObjective > objective_diffs
Diff(const ObjectiveStorage &storage, VariableId variable_checkpoint)
absl::flat_hash_set< AuxiliaryObjectiveId > deleted