OR-Tools  9.0
indexed_model.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 <string>
18 #include <utility>
19 #include <vector>
20 
21 #include "absl/container/flat_hash_map.h"
22 #include "absl/container/flat_hash_set.h"
23 #include "absl/strings/string_view.h"
24 #include "absl/types/span.h"
25 #include "ortools/base/int_type.h"
26 #include "ortools/base/logging.h"
27 #include "ortools/base/map_util.h"
28 #include "ortools/math_opt/model.pb.h"
29 #include "ortools/math_opt/model_update.pb.h"
30 #include "ortools/math_opt/result.pb.h"
31 #include "ortools/math_opt/solution.pb.h"
32 #include "ortools/math_opt/sparse_containers.pb.h"
34 
35 namespace operations_research {
36 namespace math_opt {
37 
38 namespace {
39 
40 template <typename K, typename V>
41 std::vector<K> MapKeys(const absl::flat_hash_map<K, V>& in_map) {
42  std::vector<K> keys;
43  keys.reserve(in_map.size());
44  for (const auto& key_pair : in_map) {
45  keys.push_back(key_pair.first);
46  }
47  return keys;
48 }
49 
50 template <typename K, typename V>
51 std::vector<K> SortedMapKeys(const absl::flat_hash_map<K, V>& in_map) {
52  std::vector<K> keys = MapKeys(in_map);
53  std::sort(keys.begin(), keys.end());
54  return keys;
55 }
56 
57 template <typename T>
58 std::vector<T> SortedSetKeys(const absl::flat_hash_set<T>& in_set) {
59  std::vector<T> keys;
60  keys.reserve(in_set.size());
61  for (const auto& key : in_set) {
62  keys.push_back(key);
63  }
64  std::sort(keys.begin(), keys.end());
65  return keys;
66 }
67 
68 // ids should be sorted.
69 template <typename IdType>
70 void AppendFromMapOrDefault(const absl::Span<const IdType> ids,
71  const absl::flat_hash_map<IdType, double>& values,
72  SparseDoubleVectorProto& sparse_vector) {
73  for (const IdType id : ids) {
74  sparse_vector.add_ids(id.value());
75  sparse_vector.add_values(gtl::FindWithDefault(values, id));
76  }
77 }
78 
79 // ids should be sorted.
80 template <typename IdType, typename IdIterable>
81 void AppendFromMapIfPresent(const IdIterable& ids,
82  const absl::flat_hash_map<IdType, double>& values,
83  SparseDoubleVectorProto& sparse_vector) {
84  for (const IdType id : ids) {
85  const double* const double_value = gtl::FindOrNull(values, id);
86  if (double_value != nullptr) {
87  sparse_vector.add_ids(id.value());
88  sparse_vector.add_values(*double_value);
89  }
90  }
91 }
92 
93 template <typename IdType, typename DataType>
94 void AppendFromMap(const absl::flat_hash_set<IdType>& dirty_keys,
95  const absl::flat_hash_map<IdType, DataType>& values,
96  double DataType::*field,
97  SparseDoubleVectorProto& sparse_vector) {
98  for (const IdType id : SortedSetKeys(dirty_keys)) {
99  sparse_vector.add_ids(id.value());
100  sparse_vector.add_values(values.at(id).*field);
101  }
102 }
103 
104 template <typename T>
105 absl::flat_hash_map<T, BasisStatus> SparseBasisVectorToMap(
106  const SparseBasisStatusVector& sparse_vector) {
107  absl::flat_hash_map<T, BasisStatus> result;
108  CHECK_EQ(sparse_vector.ids_size(), sparse_vector.values_size());
109  result.reserve(sparse_vector.ids_size());
110  for (const auto [id, value] : MakeView(sparse_vector)) {
111  gtl::InsertOrDie(&result, T(id), static_cast<BasisStatus>(value));
112  }
113  return result;
114 }
115 
116 } // namespace
117 
118 VariableId IndexedModel::AddVariable(const double lower_bound,
119  const double upper_bound,
120  const bool is_integer,
121  const absl::string_view name) {
122  const VariableId result = next_variable_id_++;
123  VariableData& var_data = variables_[result];
124  var_data.lower_bound = lower_bound;
125  var_data.upper_bound = upper_bound;
126  var_data.is_integer = is_integer;
127  var_data.name = name;
128  if (!lazy_matrix_columns_.empty()) {
129  gtl::InsertOrDie(&lazy_matrix_columns_, result, {});
130  }
131  return result;
132 }
133 
134 void IndexedModel::DeleteVariable(const VariableId id) {
135  CHECK(variables_.contains(id));
136  EnsureLazyMatrixColumns();
137  EnsureLazyMatrixRows();
138  linear_objective_.erase(id);
139  variables_.erase(id);
140  if (id < variables_checkpoint_) {
141  dirty_variable_deletes_.insert(id);
142  dirty_variable_lower_bounds_.erase(id);
143  dirty_variable_upper_bounds_.erase(id);
144  dirty_variable_is_integer_.erase(id);
145  dirty_linear_objective_coefficients_.erase(id);
146  }
147  for (const LinearConstraintId related_constraint :
148  lazy_matrix_columns_.at(id)) {
149  CHECK_GT(lazy_matrix_rows_.at(related_constraint).erase(id), 0);
150  CHECK_GT(linear_constraint_matrix_.erase({related_constraint, id}), 0);
151  if (id < variables_checkpoint_ &&
152  related_constraint < linear_constraints_checkpoint_) {
153  dirty_linear_constraint_matrix_keys_.erase({related_constraint, id});
154  }
155  }
156  CHECK_GT(lazy_matrix_columns_.erase(id), 0);
157 }
158 
159 std::vector<VariableId> IndexedModel::variables() const {
160  return MapKeys(variables_);
161 }
162 
163 std::vector<VariableId> IndexedModel::SortedVariables() const {
164  return SortedMapKeys(variables_);
165 }
166 
168  const double lower_bound, const double upper_bound,
169  const absl::string_view name) {
170  const LinearConstraintId result = next_linear_constraint_id_++;
171  LinearConstraintData& lin_con_data = linear_constraints_[result];
172  lin_con_data.lower_bound = lower_bound;
173  lin_con_data.upper_bound = upper_bound;
174  lin_con_data.name = name;
175  if (!lazy_matrix_rows_.empty()) {
176  gtl::InsertOrDie(&lazy_matrix_rows_, result, {});
177  }
178  return result;
179 }
180 
181 void IndexedModel::DeleteLinearConstraint(const LinearConstraintId id) {
182  CHECK(linear_constraints_.contains(id));
183  EnsureLazyMatrixColumns();
184  EnsureLazyMatrixRows();
185  linear_constraints_.erase(id);
186  if (id < linear_constraints_checkpoint_) {
187  dirty_linear_constraint_deletes_.insert(id);
188  dirty_linear_constraint_lower_bounds_.erase(id);
189  dirty_linear_constraint_upper_bounds_.erase(id);
190  }
191  for (const VariableId related_variable : lazy_matrix_rows_.at(id)) {
192  CHECK_GT(lazy_matrix_columns_.at(related_variable).erase(id), 0);
193  CHECK_GT(linear_constraint_matrix_.erase({id, related_variable}), 0);
194  if (id < linear_constraints_checkpoint_ &&
195  related_variable < variables_checkpoint_) {
196  dirty_linear_constraint_matrix_keys_.erase({id, related_variable});
197  }
198  }
199  CHECK_GT(lazy_matrix_rows_.erase(id), 0);
200 }
201 
202 std::vector<LinearConstraintId> IndexedModel::linear_constraints() const {
203  return MapKeys(linear_constraints_);
204 }
205 
206 std::vector<LinearConstraintId> IndexedModel::SortedLinearConstraints() const {
207  return SortedMapKeys(linear_constraints_);
208 }
209 
211  const {
212  return SortedMapKeys(linear_objective_);
213 }
214 
215 void IndexedModel::AppendVariable(const VariableId id,
216  VariablesProto& variables_proto) const {
217  const VariableData& var_data = variables_.at(id);
218  variables_proto.add_ids(id.value());
219  variables_proto.add_lower_bounds(var_data.lower_bound);
220  variables_proto.add_upper_bounds(var_data.upper_bound);
221  variables_proto.add_integers(var_data.is_integer);
222  variables_proto.add_names(var_data.name);
223 }
224 
225 void IndexedModel::AppendLinearConstraint(
226  const LinearConstraintId id,
227  LinearConstraintsProto& linear_constraints_proto) const {
228  const LinearConstraintData& con_impl = linear_constraints_.at(id);
229  linear_constraints_proto.add_ids(id.value());
230  linear_constraints_proto.add_lower_bounds(con_impl.lower_bound);
231  linear_constraints_proto.add_upper_bounds(con_impl.upper_bound);
232  linear_constraints_proto.add_names(con_impl.name);
233 }
234 
235 void IndexedModel::ExportLinearConstraintMatrix(
236  const absl::Span<const std::pair<LinearConstraintId, VariableId>> entries,
237  SparseDoubleMatrixProto& matrix) const {
238  matrix.mutable_row_ids()->Reserve(entries.size());
239  matrix.mutable_column_ids()->Reserve(entries.size());
240  matrix.mutable_coefficients()->Reserve(entries.size());
241  for (const auto [constraint_id, variable_id] : entries) {
242  matrix.add_row_ids(constraint_id.value());
243  matrix.add_column_ids(variable_id.value());
244  matrix.add_coefficients(gtl::FindWithDefault(linear_constraint_matrix_,
245  {constraint_id, variable_id}));
246  }
247 }
248 
249 ModelProto IndexedModel::ExportModel() const {
250  ModelProto result;
251  result.set_name(name_);
252  // Export the variables.
253  for (const VariableId variable : SortedMapKeys(variables_)) {
254  AppendVariable(variable, *result.mutable_variables());
255  }
256 
257  // Pull out the objective.
258  result.mutable_objective()->set_maximize(is_maximize_);
259  result.mutable_objective()->set_offset(objective_offset_);
260  AppendFromMapOrDefault<VariableId>(
261  SortedMapKeys(linear_objective_), linear_objective_,
262  *result.mutable_objective()->mutable_linear_coefficients());
263 
264  // Pull out the linear constraints.
265  for (const LinearConstraintId con : SortedMapKeys(linear_constraints_)) {
266  AppendLinearConstraint(con, *result.mutable_linear_constraints());
267  }
268 
269  // Pull out the constraint matrix.
270  ExportLinearConstraintMatrix(SortedMapKeys(linear_constraint_matrix_),
271  *result.mutable_linear_constraint_matrix());
272  return result;
273 }
274 
276  ModelUpdateProto result;
277  // TODO(user): these are used to efficiently extract the constraint matrix
278  // update, but it would be good to avoid calling these because they result in
279  // a large allocation.
280  EnsureLazyMatrixRows();
281  EnsureLazyMatrixColumns();
282 
283  // Variable/constraint deletions.
284  for (const VariableId del_var : SortedSetKeys(dirty_variable_deletes_)) {
285  result.add_deleted_variable_ids(del_var.value());
286  }
287  for (const LinearConstraintId del_lin_con :
288  SortedSetKeys(dirty_linear_constraint_deletes_)) {
289  result.add_deleted_linear_constraint_ids(del_lin_con.value());
290  }
291 
292  // Update the variables.
293  auto var_updates = result.mutable_variable_updates();
294  AppendFromMap(dirty_variable_lower_bounds_, variables_,
296  *var_updates->mutable_lower_bounds());
297  AppendFromMap(dirty_variable_upper_bounds_, variables_,
299  *var_updates->mutable_upper_bounds());
300 
301  for (const VariableId integer_var :
302  SortedSetKeys(dirty_variable_is_integer_)) {
303  var_updates->mutable_integers()->add_ids(integer_var.value());
304  var_updates->mutable_integers()->add_values(
305  variables_.at(integer_var).is_integer);
306  }
307  for (VariableId new_id = variables_checkpoint_; new_id < next_variable_id_;
308  ++new_id) {
309  if (variables_.contains(new_id)) {
310  AppendVariable(new_id, *result.mutable_new_variables());
311  }
312  }
313 
314  // Update the objective
315  auto obj_updates = result.mutable_objective_updates();
316  if (dirty_objective_direction_) {
317  obj_updates->set_direction_update(is_maximize_);
318  }
319  if (dirty_objective_offset_) {
320  obj_updates->set_offset_update(objective_offset_);
321  }
322  AppendFromMapOrDefault<VariableId>(
323  SortedSetKeys(dirty_linear_objective_coefficients_), linear_objective_,
324  *obj_updates->mutable_linear_coefficients());
325  // TODO(b/182567749): Once StrongInt is in absl, use
326  // AppendFromMapIfPresent<VariableId>(
327  // MakeStrongIntRange(variables_checkpoint_, next_variable_id_),
328  // linear_objective_, *obj_updates->mutable_linear_coefficients());
329  for (VariableId var_id = variables_checkpoint_; var_id < next_variable_id_;
330  ++var_id) {
331  const double* const double_value =
332  gtl::FindOrNull(linear_objective_, var_id);
333  if (double_value != nullptr) {
334  obj_updates->mutable_linear_coefficients()->add_ids(var_id.value());
335  obj_updates->mutable_linear_coefficients()->add_values(*double_value);
336  }
337  }
338 
339  // Update the linear constraints
340  auto lin_con_updates = result.mutable_linear_constraint_updates();
341  AppendFromMap(dirty_linear_constraint_lower_bounds_, linear_constraints_,
343  *lin_con_updates->mutable_lower_bounds());
344  AppendFromMap(dirty_linear_constraint_upper_bounds_, linear_constraints_,
346  *lin_con_updates->mutable_upper_bounds());
347 
348  for (LinearConstraintId new_id = linear_constraints_checkpoint_;
349  new_id < next_linear_constraint_id_; ++new_id) {
350  if (linear_constraints_.contains(new_id)) {
351  AppendLinearConstraint(new_id, *result.mutable_new_linear_constraints());
352  }
353  }
354 
355  // Extract changes to the matrix of linear constraint coefficients
356  std::vector<std::pair<LinearConstraintId, VariableId>>
357  constraint_matrix_updates(dirty_linear_constraint_matrix_keys_.begin(),
358  dirty_linear_constraint_matrix_keys_.end());
359  for (VariableId new_var = variables_checkpoint_; new_var < next_variable_id_;
360  ++new_var) {
361  if (variables_.contains(new_var)) {
362  for (const LinearConstraintId lin_con :
363  lazy_matrix_columns_.at(new_var)) {
364  constraint_matrix_updates.emplace_back(lin_con, new_var);
365  }
366  }
367  }
368  for (LinearConstraintId new_lin_con = linear_constraints_checkpoint_;
369  new_lin_con < next_linear_constraint_id_; ++new_lin_con) {
370  if (linear_constraints_.contains(new_lin_con)) {
371  for (const VariableId var : lazy_matrix_rows_.at(new_lin_con)) {
372  // NOTE(user): we will do at most twice as much as needed here.
373  if (var < variables_checkpoint_) {
374  constraint_matrix_updates.emplace_back(new_lin_con, var);
375  }
376  }
377  }
378  }
379  std::sort(constraint_matrix_updates.begin(), constraint_matrix_updates.end());
380  ExportLinearConstraintMatrix(
381  constraint_matrix_updates,
382  *result.mutable_linear_constraint_matrix_updates());
383  return result;
384 }
385 
386 void IndexedModel::EnsureLazyMatrixColumns() {
387  if (lazy_matrix_columns_.empty()) {
388  for (const auto& var_pair : variables_) {
389  lazy_matrix_columns_.insert({var_pair.first, {}});
390  }
391  for (const auto& mat_entry : linear_constraint_matrix_) {
392  lazy_matrix_columns_.at(mat_entry.first.second)
393  .insert(mat_entry.first.first);
394  }
395  }
396 }
397 
398 void IndexedModel::EnsureLazyMatrixRows() {
399  if (lazy_matrix_rows_.empty()) {
400  for (const auto& lin_con_pair : linear_constraints_) {
401  lazy_matrix_rows_.insert({lin_con_pair.first, {}});
402  }
403  for (const auto& mat_entry : linear_constraint_matrix_) {
404  lazy_matrix_rows_.at(mat_entry.first.first)
405  .insert(mat_entry.first.second);
406  }
407  }
408 }
409 
411  variables_checkpoint_ = next_variable_id_;
412  linear_constraints_checkpoint_ = next_linear_constraint_id_;
413  dirty_objective_direction_ = false;
414  dirty_objective_offset_ = false;
415 
416  dirty_variable_deletes_.clear();
417  dirty_variable_lower_bounds_.clear();
418  dirty_variable_upper_bounds_.clear();
419  dirty_variable_is_integer_.clear();
420 
421  dirty_linear_objective_coefficients_.clear();
422 
423  dirty_linear_constraint_deletes_.clear();
424  dirty_linear_constraint_lower_bounds_.clear();
425  dirty_linear_constraint_upper_bounds_.clear();
426  dirty_linear_constraint_matrix_keys_.clear();
427 }
428 
430  const SolveResultProto& solve_result) {
431  IndexedSolutions solutions;
432  for (const PrimalSolutionProto& primal_solution :
433  solve_result.primal_solutions()) {
435  p.variable_values =
436  MakeView(primal_solution.variable_values()).as_map<VariableId>();
437  p.objective_value = primal_solution.objective_value();
438  solutions.primal_solutions.push_back(std::move(p));
439  }
440  for (const PrimalRayProto& primal_ray : solve_result.primal_rays()) {
441  IndexedPrimalRay pr;
442  pr.variable_values =
443  MakeView(primal_ray.variable_values()).as_map<VariableId>();
444  solutions.primal_rays.push_back(std::move(pr));
445  }
446  for (const DualSolutionProto& dual_solution : solve_result.dual_solutions()) {
448  d.reduced_costs =
449  MakeView(dual_solution.reduced_costs()).as_map<VariableId>();
450  d.dual_values =
451  MakeView(dual_solution.dual_values()).as_map<LinearConstraintId>();
452  d.objective_value = dual_solution.objective_value();
453  solutions.dual_solutions.push_back(std::move(d));
454  }
455  for (const DualRayProto& dual_ray : solve_result.dual_rays()) {
456  IndexedDualRay dr;
457  dr.reduced_costs = MakeView(dual_ray.reduced_costs()).as_map<VariableId>();
458  dr.dual_values =
459  MakeView(dual_ray.dual_values()).as_map<LinearConstraintId>();
460  solutions.dual_rays.push_back(std::move(dr));
461  }
462  for (const BasisProto& basis : solve_result.basis()) {
463  IndexedBasis indexed_basis;
464  indexed_basis.constraint_status =
465  SparseBasisVectorToMap<LinearConstraintId>(basis.constraint_status());
466  indexed_basis.variable_status =
467  SparseBasisVectorToMap<VariableId>(basis.variable_status());
468  solutions.basis.push_back(std::move(indexed_basis));
469  }
470  return solutions;
471 }
472 
473 } // namespace math_opt
474 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:498
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define CHECK_GT(val1, val2)
Definition: base/logging.h:710
std::vector< LinearConstraintId > linear_constraints() const
std::vector< VariableId > SortedVariables() const
std::vector< VariableId > SortedLinearObjectiveNonzeroVariables() const
void DeleteLinearConstraint(LinearConstraintId id)
std::vector< VariableId > variables() const
VariableId AddVariable(absl::string_view name="")
LinearConstraintId AddLinearConstraint(absl::string_view name="")
std::vector< LinearConstraintId > SortedLinearConstraints() const
const std::string name
int64_t value
IntVar * var
Definition: expr_array.cc:1874
double upper_bound
double lower_bound
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition: map_util.h:154
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:60
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:29
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
IndexedSolutions IndexedSolutionsFromProto(const SolveResultProto &solve_result)
Collection of objects used to extend the Constraint Solver library.
absl::flat_hash_map< VariableId, BasisStatus > variable_status
absl::flat_hash_map< LinearConstraintId, BasisStatus > constraint_status
absl::flat_hash_map< VariableId, double > reduced_costs
absl::flat_hash_map< LinearConstraintId, double > dual_values
absl::flat_hash_map< VariableId, double > reduced_costs
absl::flat_hash_map< LinearConstraintId, double > dual_values
absl::flat_hash_map< VariableId, double > variable_values
absl::flat_hash_map< VariableId, double > variable_values
std::vector< IndexedDualSolution > dual_solutions
std::vector< IndexedPrimalSolution > primal_solutions
std::vector< IndexedPrimalRay > primal_rays