// Copyright 2010-2021 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // An index based C++ API for building optimization problems. // // TODO(b/188550843): move/rename to core/model_storage.h // // Models problems of the form: // min sum_{j in J} c_j * x_j + d // s.t. lb^c_i <= sum_{j in J} A_ij * x_j <= ub^c_i for all i in I, // lb^v_j <= x_j <= ub^v_j for all j in J, // x_j integer for all j in Z, // where above: // * I: the set of linear constraints, // * J: the set of variables, // * Z: a subset of J, the integer variables, // * x: the decision variables (indexed by J), // * c: the linear objective, one double per variable, // * d: the objective offset, a double scalar, // * lb^c: the constraint lower bounds, one double per linear constraint, // * ub^c: the constraint upper bounds, one double per linear constraint, // * lb^v: the variable lower bounds, one double per variable, // * ub^v: the variable upper bounds, one double per variable, // * A: the linear constraint matrix, a double per variable/constraint pair. // // The min in the objective can also be changed to a max. // // A simple example: // // Model the problem: // max 2.0 * x + y // s.t. x + y <= 1.5 // x in {0.0, 1.0} // 0 <= y <= 2.5 // // using ::operations_research::math_opt::IndexedModel; // using ::operations_research::math_opt::VariableId; // using ::operations_research::math_opt::LinearConstraintId; // using ::operations_research::math_opt::ModelProto; // using ::operations_research::math_opt::ModelProtoUpdate; // // IndexedModel model("my_model"); // const VariableId x = model.AddVariable(0.0, 1.0, true, "x"); // const VariableId y = model.AddVariable(0.0, 2.5, false, "y"); // const LinearConstraintId c = model.AddLinearConstraint( // -std::numeric_limits::infinity, 1.5, "c"); // model.set_linear_constraint_coefficient(x, c, 1.0); // model.set_linear_constraint_coefficient(y, c, 1.0); // model.set_linear_objective_coefficient(x, 2.0); // model.set_linear_objective_coefficient(y, 1.0); // model.set_maximize(); // // Now, export to a proto describing the model: // // const ModelProto model_proto = model.ExportModel(); // // Modify the problem and get a model update proto: // // const std::unique_ptr update_tracker = // model.NewUpdateTracker(); // c.set_upper_bound(2.0); // const absl::optional update_proto = // update_tracker->ExportModelUpdate(); // update_tracker->Checkpoint(); // // Reading and writing model properties: // // Properties of the model (e.g. variable/constraint bounds) can be written // and read in amortized O(1) time. Deleting a variable will take time // O(#constraints containing the variable), and likewise deleting a constraint // will take time O(#variables in the constraint). The constraint matrix is // stored as hash map where the key is a {LinearConstraintId, VariableId} // pair and the value is the coefficient. The nonzeros of the matrix are // additionally stored by row and by column, but these indices generated lazily // upon first use. Asking for the set of variables in a constraint, the // constraints in a variable, deleting a variable or constraint, or requesting a // ModelUpdate proto will all trigger these additional indices to be generated. // // Exporting the Model proto: // // The Model proto is an equivalent representation to IndexedModel. It has a // smaller memory footprint and optimized for storage/transport, rather than // efficient modification. It is also the format consumed by solvers in this // library. The Model proto can be generated by calling // IndexedModel::ExportModel(). // // Incrementalism, the ModelUpdate proto, and Checkpoints: // // To update an existing model as specified by a Model proto, solvers consume a // ModelUpdate proto, which describes the changes to a model (e.g. new variables // or a change in a variable bound). IndexedModel::UpdateTracker tracks the // changes made and produces a ModelUpdate proto describing these changes with // the method UpdateTracker::ExportModelUpdate(). The changes returned will be // the modifications since the previous call to // UpdateTracker::Checkpoint(). Note that, for newly initialized models, before // the first checkpoint, there is no additional memory overhead from tracking // changes. See // g3doc/ortools/math_opt/g3doc/model_building_complexity.md // for details. // // On bad input: // // Using a bad variable id or constraint id (an id not in the current model, // which includes ids that have been deleted) on any method will result in an // immediate failure (either a CHECK failure or an exception, which is an // implementation detail you should not rely on). We make no attempt to say if a // model is invalid (e.g. a variable lower bound is infinite, exceeds an upper // bound, or is NaN). The exported models are validated instead, see // model_validator.h. #ifndef OR_TOOLS_MATH_OPT_CORE_INDEXED_MODEL_H_ #define OR_TOOLS_MATH_OPT_CORE_INDEXED_MODEL_H_ #include #include #include #include #include #include #include #include "ortools/base/integral_types.h" #include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/meta/type_traits.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" #include "absl/types/span.h" #include "ortools/base/map_util.h" #include "ortools/base/int_type.h" #include "ortools/math_opt/model.pb.h" #include "ortools/math_opt/model_update.pb.h" #include "ortools/math_opt/result.pb.h" #include "ortools/math_opt/solution.pb.h" #include "ortools/math_opt/sparse_containers.pb.h" namespace operations_research { namespace math_opt { DEFINE_INT_TYPE(VariableId, int64_t); DEFINE_INT_TYPE(LinearConstraintId, int64_t); // A mathematical optimization model. // // Supports the efficient creation and modification of an optimization model, // and the export of Model and ModelUpdate protos. // // All methods run in amortized O(1) (as amortized over calls to that exact // function) unless otherwise specified. class IndexedModel { public: // Tracks the changes of the model. // // For each update tracker we define a checkpoint that is the starting point // used to compute the ModelUpdateProto. // // Lifecycle: the IndexedModel must outlive all the UpdateTracker instances // since they keep a reference to it. They use this reference in their // destructor so it is not safe to destroy an UpdateTracker after the // destruction of the IndexedModel. // // Thread-safety: UpdateTracker methods must not be used while modifying the // IndexedModel. The user is expected to use proper synchronization primitives // to serialize changes to the model and the use of the update trackers. The // methods of different instances of UpdateTracker are safe to be called // concurrently (i.e. multiple trackers can be called concurrently on // ExportModelUpdate() or Checkpoint()). // // Example: // IndexedModel model; // ... // ASSIGN_OR_RETURN(const auto solver, // Solver::New(solver_type, model.ExportModel(), // /*initializer=*/{})); // const std::unique_ptr update_tracker = // model.NewUpdatesTracker(); // // ASSIGN_OR_RETURN(const auto result_1, // solver->Solve(/*parameters=*/{}); // // model.AddVariable(0.0, 1.0, true, "y"); // model.set_maximize(true); // // const absl::optional update_proto = // update_tracker.ExportModelUpdate(); // update_tracker.Checkpoint(); // // if (update_proto) { // ASSIGN_OR_RETURN(const bool updated, solver->Update(*update_proto)); // if (!updated) { // // The update is not supported by the solver, we create a new one. // ASSIGN_OR_RETURN(const auto new_model_proto, model.ExportModel()); // ASSIGN_OR_RETURN(solver, // Solver::New(solver_type, new_model_proto, // /*initializer=*/{})); // } // } // ASSIGN_OR_RETURN(const auto result_2, // solver->Solve(/*parameters=*/{}); // class UpdateTracker { public: ~UpdateTracker() ABSL_LOCKS_EXCLUDED(indexed_model_.update_trackers_lock_); // Returns a proto representation of the changes to the model since the most // recent checkpoint (i.e. last time Checkpoint() was called); nullopt if // the update would have been empty. absl::optional ExportModelUpdate() ABSL_LOCKS_EXCLUDED(indexed_model_.update_trackers_lock_); // Uses the current model state as the starting point to calculate the // ModelUpdateProto next time ExportModelUpdate() is called. void Checkpoint() ABSL_LOCKS_EXCLUDED(indexed_model_.update_trackers_lock_); private: friend class IndexedModel; // Registers in update_trackers_ and calls Checkpoint() to synchronize the // checkpoint to the current state of the model. explicit UpdateTracker(IndexedModel& indexed_model) ABSL_LOCKS_EXCLUDED(indexed_model.update_trackers_lock_); // Same as Checkpoint() but the caller must have acquired the // update_trackers_lock_ mutex. void CheckpointLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(indexed_model_.update_trackers_lock_); IndexedModel& indexed_model_; // All incremental updates that occurred since last checkpoint. It is // filled-in each time Checkpoint() is called on any update tracker. When an // ExportModelUpdate() is requested on a tracker, all these are merged along // with the remaining updates. std::vector> updates_ ABSL_GUARDED_BY(indexed_model_.update_trackers_lock_); }; // Creates an empty minimization problem. explicit IndexedModel(absl::string_view name = "") : name_(name) {} // TODO(b/185892243): add `std::unique_ptr Clone()` method. IndexedModel(const IndexedModel&) = delete; IndexedModel& operator=(const IndexedModel&) = delete; inline const std::string& name() const { return name_; } ////////////////////////////////////////////////////////////////////////////// // Variables ////////////////////////////////////////////////////////////////////////////// // Adds a continuous unbounded variable to the model and returns its id. // // See AddVariable(double, double, bool, absl::string_view) for details. inline VariableId AddVariable(absl::string_view name = ""); // Adds a variable to the model and returns its id. // // The returned ids begin at zero and increase by one with each call to // AddVariable. Deleted ids are NOT reused. If no variables are deleted, // the ids in the model will be consecutive. VariableId AddVariable(double lower_bound, double upper_bound, bool is_integer, absl::string_view name = ""); inline double variable_lower_bound(VariableId id) const; inline double variable_upper_bound(VariableId id) const; inline bool is_variable_integer(VariableId id) const; inline const std::string& variable_name(VariableId id) const; inline void set_variable_lower_bound(VariableId id, double lower_bound); inline void set_variable_upper_bound(VariableId id, double upper_bound); inline void set_variable_is_integer(VariableId id, bool is_integer); inline void set_variable_as_integer(VariableId id); inline void set_variable_as_continuous(VariableId id); // Removes a variable from the model. // // It is an error to use a deleted variable id as input to any subsequent // function calls on the model. Runs in O(#constraints containing the // variable). void DeleteVariable(VariableId id); // The number of variables in the model. // // Equal to the number of variables created minus the number of variables // deleted. inline int num_variables() const; // The returned id of the next call to AddVariable. // // Equal to the number of variables created. inline VariableId next_variable_id() const; // Returns true if this id has been created and not yet deleted. inline bool has_variable(VariableId id) const; // The VariableIds in use (not deleted), order not defined. std::vector variables() const; // Returns a sorted vector of all existing (not deleted) variables in the // model. // // Runs in O(n log(n)), where n is the number of variables returned. std::vector SortedVariables() const; ////////////////////////////////////////////////////////////////////////////// // Linear Constraints ////////////////////////////////////////////////////////////////////////////// // Adds a linear constraint to the model with a lower bound of -inf and an // upper bound of +inf and returns its id. // // See AddLinearConstraint(double, double, absl::string_view) for details. inline LinearConstraintId AddLinearConstraint(absl::string_view name = ""); // Adds a linear constraint to the model returns its id. // // The returned ids begin at zero and increase by one with each call to // AddLinearConstraint. Deleted ids are NOT reused. If no linear // constraints are deleted, the ids in the model will be consecutive. LinearConstraintId AddLinearConstraint(double lower_bound, double upper_bound, absl::string_view name = ""); inline double linear_constraint_lower_bound(LinearConstraintId id) const; inline double linear_constraint_upper_bound(LinearConstraintId id) const; inline const std::string& linear_constraint_name(LinearConstraintId id) const; inline void set_linear_constraint_lower_bound(LinearConstraintId id, double lower_bound); inline void set_linear_constraint_upper_bound(LinearConstraintId id, double upper_bound); // Removes a linear constraint from the model. // // It is an error to use a deleted linear constraint id as input to any // subsequent function calls on the model. Runs in O(#variables in the linear // constraint). void DeleteLinearConstraint(LinearConstraintId id); // The number of linear constraints in the model. // // Equal to the number of linear constraints created minus the number of // linear constraints deleted. inline int num_linear_constraints() const; // The returned id of the next call to AddLinearConstraint. // // Equal to the number of linear constraints created. inline LinearConstraintId next_linear_constraint_id() const; // Returns true if this id has been created and not yet deleted. inline bool has_linear_constraint(LinearConstraintId id) const; // The LinearConstraintsIds in use (not deleted), order not defined. std::vector linear_constraints() const; // Returns a sorted vector of all existing (not deleted) linear constraints in // the model. // // Runs in O(n log(n)), where n is the number of linear constraints returned. std::vector SortedLinearConstraints() const; ////////////////////////////////////////////////////////////////////////////// // Linear constraint matrix ////////////////////////////////////////////////////////////////////////////// // Returns 0.0 if the entry is not in matrix. inline double linear_constraint_coefficient(LinearConstraintId constraint, VariableId variable) const; inline bool is_linear_constraint_coefficient_nonzero( LinearConstraintId constraint, VariableId variable) const; // Setting a value to 0.0 will delete the {constraint, variable} pair from the // underlying sparse matrix representation (and has no effect if the pair is // not present). inline void set_linear_constraint_coefficient(LinearConstraintId constraint, VariableId variable, double value); // The {linear constraint, variable} pairs with nonzero linear constraint // matrix coefficients. inline const absl::flat_hash_map, double>& linear_constraint_matrix() const; // Returns the variables with nonzero coefficients in a linear constraint. // // Runs in O(1), but triggers allocations that are O(nnz) on first use through // a lazy initialization. inline const absl::flat_hash_set& variables_in_linear_constraint( LinearConstraintId constraint); // Returns the linear constraints with nonzero coefficients on a variable. // // Runs in O(1), but triggers allocations that are O(nnz) on first use through // a lazy initialization. inline const absl::flat_hash_set& linear_constraints_with_variable(VariableId variable); ////////////////////////////////////////////////////////////////////////////// // Objective ////////////////////////////////////////////////////////////////////////////// inline bool is_maximize() const; inline double objective_offset() const; // Returns 0.0 if this variable has no linear objective coefficient. inline double linear_objective_coefficient(VariableId variable) const; inline bool is_linear_objective_coefficient_nonzero( VariableId variable) const; inline void set_is_maximize(bool is_maximize); inline void set_maximize(); inline void set_minimize(); inline void set_objective_offset(double value); // Setting a value to 0.0 will delete the variable from the underlying sparse // representation (and has no effect if the variable is not present). inline void set_linear_objective_coefficient(VariableId variable, double value); // Equivalent to calling set_linear_objective_coefficient(v, 0.0) for every // variable with nonzero objective coefficient. // // Runs in O(#variables with nonzero objective coefficient). inline void clear_objective(); // The variables with nonzero linear objective coefficients. inline const absl::flat_hash_map& linear_objective() const; // Returns a sorted vector of all variables in the model with nonzero linear // objective coefficients. // // Runs in O(n log(n)), where n is the number of variables returned. std::vector SortedLinearObjectiveNonzeroVariables() const; ////////////////////////////////////////////////////////////////////////////// // Export ////////////////////////////////////////////////////////////////////////////// // Returns a proto representation of the optimization model. ModelProto ExportModel() const; // Returns a tracker that can be used to generate a ModelUpdateProto with the // updates that happened since the last checkpoint. The tracker initial // checkpoint corresponds to the current state of the model. // // The returned UpdateTracker keeps a reference to this IndexedModel. Thus the // IndexedModel must not be destroyed before the destruction of all its // UpdateTracker instances. // // Thread-safety: this method must not be used while modifying the // IndexedModel. The user is expected to use proper synchronization primitive // to serialize changes to the model and the use of this method. std::unique_ptr NewUpdateTracker(); private: struct VariableData { double lower_bound = -std::numeric_limits::infinity(); double upper_bound = std::numeric_limits::infinity(); bool is_integer = false; std::string name = ""; }; struct LinearConstraintData { double lower_bound = -std::numeric_limits::infinity(); double upper_bound = std::numeric_limits::infinity(); std::string name = ""; }; template void set_variable_property(VariableId id, T value, T VariableData::*field, absl::flat_hash_set& dirty_set); inline void set_linear_constraint_property( const LinearConstraintId id, double value, double LinearConstraintData::*field, absl::flat_hash_set& dirty_set); // Initializes lazy_matrix_columns_ (column major storage of the linear // constraint matrix) if it is still empty and there is at least one variable // in the model. void EnsureLazyMatrixColumns(); // Initializes lazy_matrix_rows_ (row major storage of the linear constraint // matrix) if it is still empty and there is at least one linear constraint in // the model. void EnsureLazyMatrixRows(); // Export a single variable to proto. void AppendVariable(VariableId id, VariablesProto& variables_proto) const; // Export a single linear constraint to proto. void AppendLinearConstraint( LinearConstraintId id, LinearConstraintsProto& linear_constraints_proto) const; // If an element in entries is not found in linear_constraint_matrix_, it is // set to 0.0 in matrix. Entries should be sorted by constraint then by // variable. void ExportLinearConstraintMatrix( absl::Span> entries, SparseDoubleMatrixProto& matrix) const; // Returns a proto representation of the changes to the model since the most // recent call to SharedCheckpoint() or nullopt if no changes happened. // // Thread-safety: this method must not be called concurrently (due to // EnsureLazyMatrixXxx() functions). absl::optional ExportSharedModelUpdate() ABSL_EXCLUSIVE_LOCKS_REQUIRED(update_trackers_lock_); // Use the current model state as the starting point to calculate the // ModelUpdateProto next time ExportSharedModelUpdate() is called. void SharedCheckpoint() ABSL_EXCLUSIVE_LOCKS_REQUIRED(update_trackers_lock_); std::string name_; VariableId next_variable_id_ = VariableId(0); LinearConstraintId next_linear_constraint_id_ = LinearConstraintId(0); bool is_maximize_ = false; double objective_offset_ = 0.0; absl::flat_hash_map variables_; absl::flat_hash_map linear_constraints_; // The values of the map must never include zero. absl::flat_hash_map linear_objective_; // The values of the map must never include zero. absl::flat_hash_map, double> linear_constraint_matrix_; absl::flat_hash_map> lazy_matrix_columns_; absl::flat_hash_map> lazy_matrix_rows_; // Update information // // Implicitly, all data for variables and constraints added after the last // checkpoint are considered "new" and will NOT be stored in the "dirty" data // structures below. VariableId variables_checkpoint_ = VariableId(0); LinearConstraintId linear_constraints_checkpoint_ = LinearConstraintId(0); bool dirty_objective_direction_ = false; bool dirty_objective_offset_ = false; absl::flat_hash_set dirty_variable_deletes_; absl::flat_hash_set dirty_variable_lower_bounds_; absl::flat_hash_set dirty_variable_upper_bounds_; absl::flat_hash_set dirty_variable_is_integer_; absl::flat_hash_set dirty_linear_objective_coefficients_; absl::flat_hash_set dirty_linear_constraint_deletes_; absl::flat_hash_set dirty_linear_constraint_lower_bounds_; absl::flat_hash_set dirty_linear_constraint_upper_bounds_; // Only for pairs where both the variable and constraint are before the // checkpoint, i.e. // var_id < variables_checkpoint_ && // lin_con_id < linear_constraints_checkpoint_ absl::flat_hash_set> dirty_linear_constraint_matrix_keys_; // Lock used to serialize access to update_trackers_ and to the all fields of // UpdateTracker. We use only one lock since trackers are modified in group // (they share a chain of ModelUpdateProto and the update of one tracker // usually requires the update of some of the others). absl::Mutex update_trackers_lock_; // The UpdateTracker instances tracking the changes of to this model. This // collection is updated by UpdateTracker constructor and destructor. It is // used internally by UpdateTracker instances to know about other instances. absl::flat_hash_set update_trackers_ ABSL_GUARDED_BY(update_trackers_lock_); }; // A solution to the problem modeled in IndexedModel. // // IndexedPrimalSolution will satisfy (see top of file notation): // objective_value = c * variable_values + d // In addition, if feasible (as is typical), it should additionally satisfy: // vlb <= variable_values <= vub // clb <= A * variable_values <= cub // variable_values_i integer for i in I. // // For details, see go/mathopt-solutions#primal-solution. struct IndexedPrimalSolution { absl::flat_hash_map variable_values; double objective_value; }; // A direction of improving objective value that maintains feasibility. // // Indexed primal ray will satisfy (see top of file notation): // * c * x < 0 for minimization problems, c * x > 0 for maximization problems. // * A_j * x <= 0 for finite cub_j // * A_j * x >= 0 for finite clb_j // * x_i <= 0 for finite vub_i // * x_i >= 0 for finite vlb_i // where A_j are the row of matrix coefficients for the jth linear constraint. // // // Note that this alone does not prove unboundedness, you also need a feasible // point. This is a certificate of dual infeasibility for LPs, see // go/mathopt-dual#dual-inf-cert. // // For details, see go/mathopt-solutions#primal-ray. struct IndexedPrimalRay { absl::flat_hash_map variable_values; }; // A solution to the dual of the problem modeled in IndexedModel. Applies only // when the problem has a dual (e.g. LP). // // For details, see go/mathopt-dual#dual-solution. // For an primal interpretation as objective-value/optimality certificates see: // go/mathopt-solutions#opt-certificate struct IndexedDualSolution { absl::flat_hash_map dual_values; absl::flat_hash_map reduced_costs; double objective_value; }; // A direction of improving objective value that maintains feasibility for the // dual problem. Applies only when the problem has a dual (e.g. LP). // // Note that this alone does not prove dual unboundedness, you also need a dual // feasible point. // // Also, a certificate of primal infeasibility. // // For details, see // go/mathopt-dual#dual-ray // go/mathopt-solutions#primal-inf-cert struct IndexedDualRay { absl::flat_hash_map dual_values; absl::flat_hash_map reduced_costs; }; // A basis for a solution of the problem modeled in IndexedModel. Applies only // when the problem is an LP. // // For details, see go/mathopt-basis#basis. // TODO(b/171883688): consider improving consistency for matchers in // solver_matchers.h/cc (e.g. removing PrimalDualPair from that file) struct IndexedBasis { absl::flat_hash_map constraint_status; absl::flat_hash_map variable_status; }; // The solution (or potentially multiple solutions) to an LP or MIP. For LP, // if the problem is solved to completion (i.e. we don't have an error or hit a // limit), then at least one of these should be populated. struct IndexedSolutions { std::vector primal_solutions; std::vector primal_rays; std::vector dual_solutions; std::vector dual_rays; std::vector basis; }; IndexedSolutions IndexedSolutionsFromProto( const SolveResultProto& solve_result); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Inlined function implementations //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Variables //////////////////////////////////////////////////////////////////////////////// VariableId IndexedModel::AddVariable(absl::string_view name) { return AddVariable(-std::numeric_limits::infinity(), std::numeric_limits::infinity(), false, name); } double IndexedModel::variable_lower_bound(const VariableId id) const { return variables_.at(id).lower_bound; } double IndexedModel::variable_upper_bound(const VariableId id) const { return variables_.at(id).upper_bound; } bool IndexedModel::is_variable_integer(VariableId id) const { return variables_.at(id).is_integer; } const std::string& IndexedModel::variable_name(const VariableId id) const { return variables_.at(id).name; } template void IndexedModel::set_variable_property( const VariableId id, const T value, T VariableData::*const field, absl::flat_hash_set& dirty_set) { VariableData& var_data = variables_.at(id); if (var_data.*field != value) { var_data.*field = value; if (id < variables_checkpoint_) { dirty_set.insert(id); } } } void IndexedModel::set_variable_lower_bound(const VariableId id, const double lower_bound) { set_variable_property(id, lower_bound, &VariableData::lower_bound, dirty_variable_lower_bounds_); } void IndexedModel::set_variable_upper_bound(const VariableId id, const double upper_bound) { set_variable_property(id, upper_bound, &VariableData::upper_bound, dirty_variable_upper_bounds_); } void IndexedModel::set_variable_is_integer(const VariableId id, const bool is_integer) { set_variable_property(id, is_integer, &VariableData::is_integer, dirty_variable_is_integer_); } void IndexedModel::set_variable_as_integer(VariableId id) { set_variable_is_integer(id, true); } void IndexedModel::set_variable_as_continuous(VariableId id) { set_variable_is_integer(id, false); } int IndexedModel::num_variables() const { return variables_.size(); } VariableId IndexedModel::next_variable_id() const { return next_variable_id_; } bool IndexedModel::has_variable(const VariableId id) const { return variables_.contains(id); } //////////////////////////////////////////////////////////////////////////////// // Linear Constraints //////////////////////////////////////////////////////////////////////////////// LinearConstraintId IndexedModel::AddLinearConstraint(absl::string_view name) { return AddLinearConstraint(-std::numeric_limits::infinity(), std::numeric_limits::infinity(), name); } double IndexedModel::linear_constraint_lower_bound( const LinearConstraintId id) const { return linear_constraints_.at(id).lower_bound; } double IndexedModel::linear_constraint_upper_bound( const LinearConstraintId id) const { return linear_constraints_.at(id).upper_bound; } const std::string& IndexedModel::linear_constraint_name( const LinearConstraintId id) const { return linear_constraints_.at(id).name; } void IndexedModel::set_linear_constraint_property( const LinearConstraintId id, const double value, double LinearConstraintData::*const field, absl::flat_hash_set& dirty_set) { LinearConstraintData& lin_con_data = linear_constraints_.at(id); if (lin_con_data.*field != value) { lin_con_data.*field = value; if (id < linear_constraints_checkpoint_) { dirty_set.insert(id); } } } void IndexedModel::set_linear_constraint_lower_bound( const LinearConstraintId id, const double lower_bound) { set_linear_constraint_property(id, lower_bound, &LinearConstraintData::lower_bound, dirty_linear_constraint_lower_bounds_); } void IndexedModel::set_linear_constraint_upper_bound( const LinearConstraintId id, const double upper_bound) { set_linear_constraint_property(id, upper_bound, &LinearConstraintData::upper_bound, dirty_linear_constraint_upper_bounds_); } int IndexedModel::num_linear_constraints() const { return linear_constraints_.size(); } LinearConstraintId IndexedModel::next_linear_constraint_id() const { return next_linear_constraint_id_; } bool IndexedModel::has_linear_constraint(const LinearConstraintId id) const { return linear_constraints_.contains(id); } //////////////////////////////////////////////////////////////////////////////// // Linear Constraint Matrix //////////////////////////////////////////////////////////////////////////////// double IndexedModel::linear_constraint_coefficient( LinearConstraintId constraint, VariableId variable) const { return gtl::FindWithDefault(linear_constraint_matrix_, {constraint, variable}); } bool IndexedModel::is_linear_constraint_coefficient_nonzero( LinearConstraintId constraint, VariableId variable) const { return linear_constraint_matrix_.contains({constraint, variable}); } void IndexedModel::set_linear_constraint_coefficient( LinearConstraintId constraint, VariableId variable, double value) { bool was_updated = false; if (value == 0.0) { if (linear_constraint_matrix_.erase({constraint, variable}) > 0) { was_updated = true; if (!lazy_matrix_columns_.empty()) { lazy_matrix_columns_.at(variable).erase(constraint); } if (!lazy_matrix_rows_.empty()) { lazy_matrix_rows_.at(constraint).erase(variable); } } } else { const auto [iterator, inserted] = linear_constraint_matrix_.try_emplace({constraint, variable}, value); if (inserted) { was_updated = true; } else if (iterator->second != value) { iterator->second = value; was_updated = true; } if (!lazy_matrix_columns_.empty()) { lazy_matrix_columns_.at(variable).insert(constraint); } if (!lazy_matrix_rows_.empty()) { lazy_matrix_rows_.at(constraint).insert(variable); } } if (was_updated && constraint < linear_constraints_checkpoint_ && variable < variables_checkpoint_) { dirty_linear_constraint_matrix_keys_.emplace(constraint, variable); } } const absl::flat_hash_map, double>& IndexedModel::linear_constraint_matrix() const { return linear_constraint_matrix_; } const absl::flat_hash_set& IndexedModel::variables_in_linear_constraint(LinearConstraintId constraint) { EnsureLazyMatrixRows(); return lazy_matrix_rows_.at(constraint); } const absl::flat_hash_set& IndexedModel::linear_constraints_with_variable(VariableId variable) { EnsureLazyMatrixColumns(); return lazy_matrix_columns_.at(variable); } //////////////////////////////////////////////////////////////////////////////// // Objective //////////////////////////////////////////////////////////////////////////////// bool IndexedModel::is_maximize() const { return is_maximize_; } double IndexedModel::objective_offset() const { return objective_offset_; } double IndexedModel::linear_objective_coefficient(VariableId variable) const { return gtl::FindWithDefault(linear_objective_, variable); } bool IndexedModel::is_linear_objective_coefficient_nonzero( VariableId variable) const { return linear_objective_.contains(variable); } void IndexedModel::set_is_maximize(bool is_maximize) { if (is_maximize_ != is_maximize) { dirty_objective_direction_ = true; is_maximize_ = is_maximize; } } void IndexedModel::set_maximize() { set_is_maximize(true); } void IndexedModel::set_minimize() { set_is_maximize(false); } void IndexedModel::set_objective_offset(double value) { if (value != objective_offset_) { dirty_objective_offset_ = true; objective_offset_ = value; } } void IndexedModel::set_linear_objective_coefficient(VariableId variable, double value) { bool was_updated = false; if (value == 0.0) { if (linear_objective_.erase(variable) > 0) { was_updated = true; } } else { const auto [iterator, inserted] = linear_objective_.try_emplace(variable, value); if (inserted) { was_updated = true; } else if (iterator->second != value) { iterator->second = value; was_updated = true; } } if (was_updated && variable < variables_checkpoint_) { dirty_linear_objective_coefficients_.insert(variable); } } void IndexedModel::clear_objective() { set_objective_offset(0.0); while (!linear_objective_.empty()) { set_linear_objective_coefficient(linear_objective_.begin()->first, 0.0); } } const absl::flat_hash_map& IndexedModel::linear_objective() const { return linear_objective_; } } // namespace math_opt } // namespace operations_research #endif // OR_TOOLS_MATH_OPT_CORE_INDEXED_MODEL_H_