23 #include "absl/container/flat_hash_map.h" 24 #include "absl/container/flat_hash_set.h" 25 #include "absl/memory/memory.h" 26 #include "absl/strings/string_view.h" 27 #include "absl/synchronization/mutex.h" 28 #include "absl/types/optional.h" 29 #include "absl/types/span.h" 34 #include "ortools/math_opt/model.pb.h" 35 #include "ortools/math_opt/model_update.pb.h" 36 #include "ortools/math_opt/result.pb.h" 37 #include "ortools/math_opt/solution.pb.h" 38 #include "ortools/math_opt/sparse_containers.pb.h" 45 template <
typename K,
typename V>
46 std::vector<K> MapKeys(
const absl::flat_hash_map<K, V>& in_map) {
48 keys.reserve(in_map.size());
49 for (
const auto& key_pair : in_map) {
50 keys.push_back(key_pair.first);
55 template <
typename K,
typename V>
56 std::vector<K> SortedMapKeys(
const absl::flat_hash_map<K, V>& in_map) {
57 std::vector<K> keys = MapKeys(in_map);
58 std::sort(keys.begin(), keys.end());
63 std::vector<T> SortedSetKeys(
const absl::flat_hash_set<T>& in_set) {
65 keys.reserve(in_set.size());
66 for (
const auto& key : in_set) {
69 std::sort(keys.begin(), keys.end());
74 template <
typename IdType>
75 void AppendFromMapOrDefault(
const absl::Span<const IdType> ids,
76 const absl::flat_hash_map<IdType, double>& values,
77 SparseDoubleVectorProto& sparse_vector) {
78 for (
const IdType
id : ids) {
79 sparse_vector.add_ids(
id.
value());
85 template <
typename IdType,
typename IdIterable>
86 void AppendFromMapIfPresent(
const IdIterable& ids,
87 const absl::flat_hash_map<IdType, double>& values,
88 SparseDoubleVectorProto& sparse_vector) {
89 for (
const IdType
id : ids) {
91 if (double_value !=
nullptr) {
92 sparse_vector.add_ids(
id.
value());
93 sparse_vector.add_values(*double_value);
98 template <
typename IdType,
typename DataType>
99 void AppendFromMap(
const absl::flat_hash_set<IdType>& dirty_keys,
100 const absl::flat_hash_map<IdType, DataType>& values,
101 double DataType::*field,
102 SparseDoubleVectorProto& sparse_vector) {
103 for (
const IdType
id : SortedSetKeys(dirty_keys)) {
104 sparse_vector.add_ids(
id.
value());
105 sparse_vector.add_values(values.at(
id).*field);
109 template <
typename T>
110 absl::flat_hash_map<T, BasisStatus> SparseBasisVectorToMap(
111 const SparseBasisStatusVector& sparse_vector) {
112 absl::flat_hash_map<T, BasisStatus> result;
113 CHECK_EQ(sparse_vector.ids_size(), sparse_vector.values_size());
114 result.reserve(sparse_vector.ids_size());
125 const bool is_integer,
126 const absl::string_view
name) {
127 const VariableId result = next_variable_id_++;
128 VariableData& var_data = variables_[result];
131 var_data.is_integer = is_integer;
132 var_data.name =
name;
133 if (!lazy_matrix_columns_.empty()) {
140 CHECK(variables_.contains(
id));
141 EnsureLazyMatrixColumns();
142 EnsureLazyMatrixRows();
143 linear_objective_.erase(
id);
144 variables_.erase(
id);
145 if (
id < variables_checkpoint_) {
146 dirty_variable_deletes_.insert(
id);
147 dirty_variable_lower_bounds_.erase(
id);
148 dirty_variable_upper_bounds_.erase(
id);
149 dirty_variable_is_integer_.erase(
id);
150 dirty_linear_objective_coefficients_.erase(
id);
152 for (
const LinearConstraintId related_constraint :
153 lazy_matrix_columns_.at(
id)) {
154 CHECK_GT(lazy_matrix_rows_.at(related_constraint).erase(
id), 0);
155 CHECK_GT(linear_constraint_matrix_.erase({related_constraint, id}), 0);
156 if (
id < variables_checkpoint_ &&
157 related_constraint < linear_constraints_checkpoint_) {
158 dirty_linear_constraint_matrix_keys_.erase({related_constraint,
id});
161 CHECK_GT(lazy_matrix_columns_.erase(
id), 0);
165 return MapKeys(variables_);
169 return SortedMapKeys(variables_);
174 const absl::string_view
name) {
175 const LinearConstraintId result = next_linear_constraint_id_++;
176 LinearConstraintData& lin_con_data = linear_constraints_[result];
179 lin_con_data.name =
name;
180 if (!lazy_matrix_rows_.empty()) {
187 CHECK(linear_constraints_.contains(
id));
188 EnsureLazyMatrixColumns();
189 EnsureLazyMatrixRows();
190 linear_constraints_.erase(
id);
191 if (
id < linear_constraints_checkpoint_) {
192 dirty_linear_constraint_deletes_.insert(
id);
193 dirty_linear_constraint_lower_bounds_.erase(
id);
194 dirty_linear_constraint_upper_bounds_.erase(
id);
196 for (
const VariableId related_variable : lazy_matrix_rows_.at(
id)) {
197 CHECK_GT(lazy_matrix_columns_.at(related_variable).erase(
id), 0);
198 CHECK_GT(linear_constraint_matrix_.erase({id, related_variable}), 0);
199 if (
id < linear_constraints_checkpoint_ &&
200 related_variable < variables_checkpoint_) {
201 dirty_linear_constraint_matrix_keys_.erase({id, related_variable});
204 CHECK_GT(lazy_matrix_rows_.erase(
id), 0);
208 return MapKeys(linear_constraints_);
212 return SortedMapKeys(linear_constraints_);
217 return SortedMapKeys(linear_objective_);
220 void IndexedModel::AppendVariable(
const VariableId
id,
221 VariablesProto& variables_proto)
const {
222 const VariableData& var_data = variables_.at(
id);
223 variables_proto.add_ids(
id.
value());
224 variables_proto.add_lower_bounds(var_data.lower_bound);
225 variables_proto.add_upper_bounds(var_data.upper_bound);
226 variables_proto.add_integers(var_data.is_integer);
227 variables_proto.add_names(var_data.name);
230 void IndexedModel::AppendLinearConstraint(
231 const LinearConstraintId
id,
232 LinearConstraintsProto& linear_constraints_proto)
const {
233 const LinearConstraintData& con_impl = linear_constraints_.at(
id);
234 linear_constraints_proto.add_ids(
id.
value());
235 linear_constraints_proto.add_lower_bounds(con_impl.lower_bound);
236 linear_constraints_proto.add_upper_bounds(con_impl.upper_bound);
237 linear_constraints_proto.add_names(con_impl.name);
240 void IndexedModel::ExportLinearConstraintMatrix(
241 const absl::Span<
const std::pair<LinearConstraintId, VariableId>> entries,
242 SparseDoubleMatrixProto& matrix)
const {
243 matrix.mutable_row_ids()->Reserve(entries.size());
244 matrix.mutable_column_ids()->Reserve(entries.size());
245 matrix.mutable_coefficients()->Reserve(entries.size());
246 for (
const auto [constraint_id, variable_id] : entries) {
247 matrix.add_row_ids(constraint_id.value());
248 matrix.add_column_ids(variable_id.value());
250 {constraint_id, variable_id}));
256 result.set_name(name_);
258 for (
const VariableId variable : SortedMapKeys(variables_)) {
259 AppendVariable(variable, *result.mutable_variables());
263 result.mutable_objective()->set_maximize(is_maximize_);
264 result.mutable_objective()->set_offset(objective_offset_);
265 AppendFromMapOrDefault<VariableId>(
266 SortedMapKeys(linear_objective_), linear_objective_,
267 *result.mutable_objective()->mutable_linear_coefficients());
270 for (
const LinearConstraintId con : SortedMapKeys(linear_constraints_)) {
271 AppendLinearConstraint(con, *result.mutable_linear_constraints());
275 ExportLinearConstraintMatrix(SortedMapKeys(linear_constraint_matrix_),
276 *result.mutable_linear_constraint_matrix());
280 absl::optional<ModelUpdateProto> IndexedModel::ExportSharedModelUpdate() {
283 if (variables_checkpoint_ == next_variable_id_ &&
284 linear_constraints_checkpoint_ == next_linear_constraint_id_ &&
285 !dirty_objective_direction_ && !dirty_objective_offset_ &&
286 dirty_variable_deletes_.empty() && dirty_variable_lower_bounds_.empty() &&
287 dirty_variable_upper_bounds_.empty() &&
288 dirty_variable_is_integer_.empty() &&
289 dirty_linear_objective_coefficients_.empty() &&
290 dirty_linear_constraint_deletes_.empty() &&
291 dirty_linear_constraint_lower_bounds_.empty() &&
292 dirty_linear_constraint_upper_bounds_.empty() &&
293 dirty_linear_constraint_matrix_keys_.empty()) {
294 return absl::nullopt;
300 EnsureLazyMatrixRows();
301 EnsureLazyMatrixColumns();
303 ModelUpdateProto result;
306 for (
const VariableId del_var : SortedSetKeys(dirty_variable_deletes_)) {
307 result.add_deleted_variable_ids(del_var.value());
309 for (
const LinearConstraintId del_lin_con :
310 SortedSetKeys(dirty_linear_constraint_deletes_)) {
311 result.add_deleted_linear_constraint_ids(del_lin_con.value());
315 auto var_updates = result.mutable_variable_updates();
316 AppendFromMap(dirty_variable_lower_bounds_, variables_,
318 *var_updates->mutable_lower_bounds());
319 AppendFromMap(dirty_variable_upper_bounds_, variables_,
321 *var_updates->mutable_upper_bounds());
323 for (
const VariableId integer_var :
324 SortedSetKeys(dirty_variable_is_integer_)) {
325 var_updates->mutable_integers()->add_ids(integer_var.value());
326 var_updates->mutable_integers()->add_values(
327 variables_.at(integer_var).is_integer);
329 for (VariableId new_id = variables_checkpoint_; new_id < next_variable_id_;
331 if (variables_.contains(new_id)) {
332 AppendVariable(new_id, *result.mutable_new_variables());
337 auto obj_updates = result.mutable_objective_updates();
338 if (dirty_objective_direction_) {
339 obj_updates->set_direction_update(is_maximize_);
341 if (dirty_objective_offset_) {
342 obj_updates->set_offset_update(objective_offset_);
344 AppendFromMapOrDefault<VariableId>(
345 SortedSetKeys(dirty_linear_objective_coefficients_), linear_objective_,
346 *obj_updates->mutable_linear_coefficients());
351 for (VariableId var_id = variables_checkpoint_; var_id < next_variable_id_;
353 const double*
const double_value =
355 if (double_value !=
nullptr) {
356 obj_updates->mutable_linear_coefficients()->add_ids(var_id.value());
357 obj_updates->mutable_linear_coefficients()->add_values(*double_value);
362 auto lin_con_updates = result.mutable_linear_constraint_updates();
363 AppendFromMap(dirty_linear_constraint_lower_bounds_, linear_constraints_,
365 *lin_con_updates->mutable_lower_bounds());
366 AppendFromMap(dirty_linear_constraint_upper_bounds_, linear_constraints_,
368 *lin_con_updates->mutable_upper_bounds());
370 for (LinearConstraintId new_id = linear_constraints_checkpoint_;
371 new_id < next_linear_constraint_id_; ++new_id) {
372 if (linear_constraints_.contains(new_id)) {
373 AppendLinearConstraint(new_id, *result.mutable_new_linear_constraints());
378 std::vector<std::pair<LinearConstraintId, VariableId>>
379 constraint_matrix_updates(dirty_linear_constraint_matrix_keys_.begin(),
380 dirty_linear_constraint_matrix_keys_.end());
381 for (VariableId new_var = variables_checkpoint_; new_var < next_variable_id_;
383 if (variables_.contains(new_var)) {
384 for (
const LinearConstraintId lin_con :
385 lazy_matrix_columns_.at(new_var)) {
386 constraint_matrix_updates.emplace_back(lin_con, new_var);
390 for (LinearConstraintId new_lin_con = linear_constraints_checkpoint_;
391 new_lin_con < next_linear_constraint_id_; ++new_lin_con) {
392 if (linear_constraints_.contains(new_lin_con)) {
393 for (
const VariableId
var : lazy_matrix_rows_.at(new_lin_con)) {
395 if (
var < variables_checkpoint_) {
396 constraint_matrix_updates.emplace_back(new_lin_con,
var);
401 std::sort(constraint_matrix_updates.begin(), constraint_matrix_updates.end());
402 ExportLinearConstraintMatrix(
403 constraint_matrix_updates,
404 *result.mutable_linear_constraint_matrix_updates());
409 return {std::move(result)};
412 void IndexedModel::EnsureLazyMatrixColumns() {
413 if (lazy_matrix_columns_.empty()) {
414 for (
const auto& var_pair : variables_) {
415 lazy_matrix_columns_.insert({var_pair.first, {}});
417 for (
const auto& mat_entry : linear_constraint_matrix_) {
418 lazy_matrix_columns_.at(mat_entry.first.second)
419 .insert(mat_entry.first.first);
424 void IndexedModel::EnsureLazyMatrixRows() {
425 if (lazy_matrix_rows_.empty()) {
426 for (
const auto& lin_con_pair : linear_constraints_) {
427 lazy_matrix_rows_.insert({lin_con_pair.first, {}});
429 for (
const auto& mat_entry : linear_constraint_matrix_) {
430 lazy_matrix_rows_.at(mat_entry.first.first)
431 .insert(mat_entry.first.second);
436 void IndexedModel::SharedCheckpoint() {
437 variables_checkpoint_ = next_variable_id_;
438 linear_constraints_checkpoint_ = next_linear_constraint_id_;
439 dirty_objective_direction_ =
false;
440 dirty_objective_offset_ =
false;
442 dirty_variable_deletes_.clear();
443 dirty_variable_lower_bounds_.clear();
444 dirty_variable_upper_bounds_.clear();
445 dirty_variable_is_integer_.clear();
447 dirty_linear_objective_coefficients_.clear();
449 dirty_linear_constraint_deletes_.clear();
450 dirty_linear_constraint_lower_bounds_.clear();
451 dirty_linear_constraint_upper_bounds_.clear();
452 dirty_linear_constraint_matrix_keys_.clear();
456 const SolveResultProto& solve_result) {
458 for (
const PrimalSolutionProto& primal_solution :
459 solve_result.primal_solutions()) {
462 MakeView(primal_solution.variable_values()).as_map<VariableId>();
466 for (
const PrimalRayProto& primal_ray : solve_result.primal_rays()) {
469 MakeView(primal_ray.variable_values()).as_map<VariableId>();
472 for (
const DualSolutionProto& dual_solution : solve_result.dual_solutions()) {
475 MakeView(dual_solution.reduced_costs()).as_map<VariableId>();
477 MakeView(dual_solution.dual_values()).as_map<LinearConstraintId>();
481 for (
const DualRayProto& dual_ray : solve_result.dual_rays()) {
485 MakeView(dual_ray.dual_values()).as_map<LinearConstraintId>();
486 solutions.
dual_rays.push_back(std::move(dr));
488 for (
const BasisProto& basis : solve_result.basis()) {
491 SparseBasisVectorToMap<LinearConstraintId>(basis.constraint_status());
493 SparseBasisVectorToMap<VariableId>(basis.variable_status());
494 solutions.
basis.push_back(std::move(indexed_basis));
507 IndexedModel::UpdateTracker::UpdateTracker(
IndexedModel& indexed_model)
508 : indexed_model_(indexed_model) {
509 absl::MutexLock lock(&indexed_model_.update_trackers_lock_);
510 CHECK(indexed_model_.update_trackers_.insert(
this).second);
515 absl::MutexLock lock(&indexed_model_.update_trackers_lock_);
516 CHECK(indexed_model_.update_trackers_.erase(
this));
519 absl::optional<ModelUpdateProto>
521 absl::MutexLock lock(&indexed_model_.update_trackers_lock_);
526 if (updates_.empty()) {
527 return indexed_model_.ExportSharedModelUpdate();
532 std::vector<UpdateTracker*> all_trackers_at_checkpoint;
533 bool found_this =
false;
534 for (
UpdateTracker*
const tracker : indexed_model_.update_trackers_) {
535 if (!tracker->updates_.empty() &&
536 tracker->updates_.front() == updates_.front()) {
540 if (tracker ==
this) {
543 all_trackers_at_checkpoint.push_back(tracker);
547 CHECK_EQ(updates_.size(), tracker->updates_.size());
549 for (
int i = 0; i < updates_.size(); ++i) {
550 CHECK_EQ(updates_[i], tracker->updates_[i])
551 <<
"Two trackers have the same checkpoint but different updates.";
568 const auto merge = std::make_shared<ModelUpdateProto>();
569 for (
const auto& update : updates_) {
575 for (
UpdateTracker*
const tracker : all_trackers_at_checkpoint) {
576 tracker->updates_.clear();
577 tracker->updates_.push_back(merge);
580 ModelUpdateProto update = *merge;
581 const absl::optional<ModelUpdateProto> pending_update =
582 indexed_model_.ExportSharedModelUpdate();
583 if (pending_update) {
590 return {std::move(update)};
594 absl::MutexLock lock(&indexed_model_.update_trackers_lock_);
599 void IndexedModel::UpdateTracker::CheckpointLocked() {
603 if (indexed_model_.update_trackers_.size() == 1) {
604 CHECK(*indexed_model_.update_trackers_.begin() ==
this);
606 absl::optional<ModelUpdateProto> update =
607 indexed_model_.ExportSharedModelUpdate();
609 const auto shared_update =
610 std::make_shared<ModelUpdateProto>(*std::move(update));
612 bool found_this =
false;
613 for (UpdateTracker*
const tracker : indexed_model_.update_trackers_) {
614 if (tracker ==
this) {
617 tracker->updates_.push_back(shared_update);
622 indexed_model_.SharedCheckpoint();
std::vector< IndexedPrimalSolution > primal_solutions
std::vector< VariableId > variables() const
LinearConstraintId AddLinearConstraint(absl::string_view name="")
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
~UpdateTracker() ABSL_LOCKS_EXCLUDED(indexed_model_.update_trackers_lock_)
absl::flat_hash_map< VariableId, BasisStatus > variable_status
absl::flat_hash_map< VariableId, double > reduced_costs
#define CHECK_GT(val1, val2)
VariableId AddVariable(absl::string_view name="")
std::vector< IndexedDualRay > dual_rays
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
absl::flat_hash_map< VariableId, double > reduced_costs
absl::flat_hash_map< VariableId, double > variable_values
std::vector< IndexedDualSolution > dual_solutions
std::vector< LinearConstraintId > SortedLinearConstraints() const
void MergeIntoUpdate(const ModelUpdateProto &from, ModelUpdateProto &into)
std::unique_ptr< UpdateTracker > NewUpdateTracker()
absl::optional< ModelUpdateProto > ExportModelUpdate() ABSL_LOCKS_EXCLUDED(indexed_model_.update_trackers_lock_)
absl::flat_hash_map< LinearConstraintId, double > dual_values
std::vector< VariableId > SortedVariables() const
const std::string & name() const
absl::flat_hash_map< LinearConstraintId, BasisStatus > constraint_status
#define CHECK_EQ(val1, val2)
void Checkpoint() ABSL_LOCKS_EXCLUDED(indexed_model_.update_trackers_lock_)
void DeleteLinearConstraint(LinearConstraintId id)
IndexedSolutions IndexedSolutionsFromProto(const SolveResultProto &solve_result)
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)
absl::flat_hash_map< LinearConstraintId, double > dual_values
std::vector< LinearConstraintId > linear_constraints() const
absl::flat_hash_map< VariableId, double > variable_values
Collection of objects used to extend the Constraint Solver library.
std::vector< IndexedBasis > basis
std::vector< VariableId > SortedLinearObjectiveNonzeroVariables() const
const Collection::value_type::second_type * FindOrNull(const Collection &collection, const typename Collection::value_type::first_type &key)
std::vector< IndexedPrimalRay > primal_rays
void DeleteVariable(VariableId id)
ModelProto ExportModel() const