// Copyright (c) 2018 Liangliang Nan. All rights reserved. // // This file is part of CGAL (www.cgal.org) // // $URL: https://github.com/CGAL/cgal/blob/v5.1/Solver_interface/include/CGAL/Mixed_integer_program_traits.h $ // $Id: Mixed_integer_program_traits.h 202430d 2020-04-20T15:51:48+02:00 Dmitry Anisimov // SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial // // Author(s) : Liangliang Nan #ifndef CGAL_MIXED_INTEGER_PROGRAM_TRAITS_H #define CGAL_MIXED_INTEGER_PROGRAM_TRAITS_H #include #include #include #include #include #include #include namespace CGAL { /// \cond SKIP_IN_MANUAL // forward declaration template class Mixed_integer_program_traits; /// The base class of solver element(e.g., Variable, Linear_constraint, and Linear_objective) in /// a mixed integer program template class Solver_entry { public: typedef CGAL::Mixed_integer_program_traits Solver; private: /// A solver element (e.g., variable, constraint, objective) cannot belong to multiple solvers. /// "solver" owns this entry. Solver_entry( Solver* solver, const std::string& name = "", int idx = 0 ) : solver_(solver), name_(name), index_(idx) {} public: const std::string& name() const { return name_; } void set_name(const std::string& n) { name_ = n; } int index() const { return index_; } void set_index(int idx) { index_ = idx; } /// The solver that owns this entry const Solver* solver() const { return solver_; } Solver* solver() { return solver_; } private: Solver* solver_; // The solver that owns this entry std::string name_; int index_; template friend class Variable; template friend class Linear_expression; template friend class Mixed_integer_program_traits; }; /// The base class of solver elements that have bound constraints. template class Bound { private: Bound(FT lb = -infinity(), FT ub = +infinity()); public: void set_bounds(FT lb, FT ub); void set_lower_bound(FT lb) { lower_bound_ = lb; } void set_upper_bound(FT ub) { upper_bound_ = ub; } void get_bounds(FT& lb, FT& ub) const; FT lower_bound() const { return lower_bound_; } FT upper_bound() const { return upper_bound_; } static FT infinity(); private: FT lower_bound_; FT upper_bound_; static FT infinity_; template friend class Variable; template friend class Linear_constraint; template friend class Mixed_integer_program_traits; }; /// \endcond /// \ingroup PkgSolverInterfaceRef /// /// The variable of a mixed integer program. /// /// \cgalModels `MixedIntegerProgramVariable` template class Variable : public Solver_entry, public Bound { /// \cond SKIP_IN_MANUAL public: enum Variable_type { CONTINUOUS, INTEGER, BINARY }; typedef CGAL::Bound Bound; typedef CGAL::Solver_entry Solver_entry; typedef CGAL::Mixed_integer_program_traits Solver; private: /// A variable cannot belong to several solvers. /// "solver" owns this variable. Variable( Solver* solver, Variable_type type = CONTINUOUS, FT lb = -Bound::infinity(), FT ub = +Bound::infinity(), const std::string& name = "", int idx = 0 ); public: Variable_type variable_type() const { return variable_type_; } void set_variable_type(Variable_type t); /// Returns the value of the variable in the current solution. /// \note (1) Valid only if the program was successfully solved. /// (2) If the variable is integer and rounded == true, then the /// value will be rounded to the nearest integer. FT solution_value(bool rounded = false) const; void set_solution_value(FT value) { solution_value_ = value; } private: Variable_type variable_type_; FT solution_value_; template friend class Mixed_integer_program_traits; /// \endcond }; /// \cond SKIP_IN_MANUAL /// The base class of Linear_constraint and Linear_objective. template class Linear_expression : public Solver_entry { public: typedef CGAL::Solver_entry Solver_entry; typedef CGAL::Variable Variable; typedef CGAL::Mixed_integer_program_traits Solver; private: /// An expression cannot belong to several solvers. /// "solver" owns this expression. Linear_expression( Solver* solver, const std::string& name = "", int idx = 0 ); public: /// Adds a coefficient to a variable. void add_coefficient(const Variable* var, FT coeff); const std::unordered_map& coefficients() const { return coefficients_; } void set_coefficients(const std::unordered_map& coeffs) { coefficients_ = coeffs; } FT get_coefficient(const Variable* var) const; // The constant term void set_offset(FT value) { offset_ = value; } FT offset() const { return offset_; } /// Evaluates the value of this expression at the solution found. /// \note (1) valid only if the problem was successfully solved. /// (2) if a variable is integer and rounded == true, then the /// variable value will be rounded to the nearest integer. FT solution_value(bool rounded = false) const; virtual void clear() { coefficients_.clear(); offset_ = 0.0; } private: std::unordered_map coefficients_; FT offset_; template friend class Linear_constraint; template friend class Linear_objective; template friend class Mixed_integer_program_traits; }; /// \endcond /// \ingroup PkgSolverInterfaceRef /// /// The linear constraint of a mixed integer program. /// /// \cgalModels `MixedIntegerProgramLinearConstraint` template class Linear_constraint : public Linear_expression, public Bound { /// \cond SKIP_IN_MANUAL public: typedef CGAL::Bound Bound; typedef CGAL::Linear_expression Linear_expression; typedef CGAL::Mixed_integer_program_traits Solver; private: /// A constraint cannot belong to several solvers. /// The "solver" owns this constraint. Linear_constraint( Solver* solver, FT lb = -Bound::infinity(), FT ub = +Bound::infinity(), const std::string& name = "", int idx = 0 ); virtual ~Linear_constraint() {} template friend class Mixed_integer_program_traits; /// \endcond }; /// \ingroup PkgSolverInterfaceRef /// /// The linear objective of a mixed integer program. /// /// \cgalModels `MixedIntegerProgramLinearObjective` /// template class Linear_objective : public Linear_expression { /// \cond SKIP_IN_MANUAL public: typedef CGAL::Mixed_integer_program_traits Solver; typedef CGAL::Linear_expression Linear_expression; typedef typename Linear_expression::Solver_entry Solver_entry; enum Sense { MINIMIZE, MAXIMIZE, UNDEFINED }; private: /// An objective cannot belong to several solvers. /// "solver" owns this objective. Linear_objective(Solver* solver, Sense sense); virtual ~Linear_objective() {} public: void set_sense(Sense sense) { sense_ = sense; } Sense sense() const { return sense_; } void clear(); private: Sense sense_; template friend class Mixed_integer_program_traits; /// \endcond }; /// \ingroup PkgSolverInterfaceRef /// /// The class `CGAL::Mixed_integer_program_traits` provides an interface for /// formulating and solving (constrained or unconstrained) mixed integer /// programs. It can also be used for general linear programs. /// \note The solve() function is virtual and thus this class cannot be /// instantiated directly. Client code should use the inherited /// classes, i.e., `CGAL::GLPK_mixed_integer_program_traits` or /// `CGAL::SCIP_mixed_integer_program_traits`. Alternatively, use /// `CGAL::Mixed_integer_program_traits` as a base to derive a new model /// (using e.g., CBC , /// Gurobi for better /// performance). /// /// \cond SKIP_IN_MANUAL /// \tparam FT Number type /// \endcond /// /// \cgalModels `MixedIntegerProgramTraits` template class Mixed_integer_program_traits { /// \cond SKIP_IN_MANUAL public: typedef CGAL::Variable Variable; typedef CGAL::Linear_constraint Linear_constraint; typedef CGAL::Linear_objective Linear_objective; typedef typename Linear_objective::Sense Sense; typedef typename Variable::Variable_type Variable_type; public: Mixed_integer_program_traits(); ~Mixed_integer_program_traits(); /// Creates a single variable, add it to the solver, and returns its pointer. /// \note (1) If name is empty or not provided, a default name (e.g., x0, x1...) /// will be given. /// (2) Memory is managed by the solver and will be automatically released /// when the solver is destroyed. Variable* create_variable( Variable_type type = Variable::CONTINUOUS, FT lb = -Variable::infinity(), FT ub = +Variable::infinity(), const std::string& name = "" ); /// Creates a set of variables and add them to the solver. /// \note (1) Variables will be given default names, e.g., x0, x1... /// (2) Memory is managed by the solver and will be automatically released /// when the solver is destroyed. std::vector create_variables(std::size_t n); /// Creates a single linear constraint, add it to the solver, and returns the pointer. /// \note (1) If 'name' is empty or not provided, a default name (e.g., c0, c1...) will be given. /// (2) Memory is managed by the solver and will be automatically released when the /// solver is destroyed. Linear_constraint* create_constraint( FT lb = -Variable::infinity(), FT ub = +Variable::infinity(), const std::string& name = "" ); /// Creates a set of linear constraints and add them to the solver. /// \note (1) Constraints with be given default names, e.g., c0, c1... /// (2) Memory is managed by the solver and will be automatically released /// when the solver is destroyed. std::vector create_constraints(std::size_t n); /// Creates the objective function and returns the pointer. /// \note Memory is managed by the solver and will be automatically released /// when the solver is destroyed. Linear_objective* create_objective(Sense sense = Linear_objective::MINIMIZE); std::size_t number_of_variables() const { return variables_.size(); } const std::vector& variables() const { return variables_; } std::vector& variables() { return variables_; } std::size_t number_of_constraints() const { return constraints_.size(); } const std::vector& constraints() const { return constraints_; } std::vector& constraints() { return constraints_; } const Linear_objective* objective() const; Linear_objective* objective(); std::size_t number_of_continuous_variables() const; std::size_t number_of_integer_variables() const; std::size_t number_of_binary_variables() const; bool is_continuous() const; // Returns true if all variables are continuous bool is_mixed_integer_program() const; // Returns true if mixed integer program bool is_integer_program() const; // Returns true if integer program bool is_binary_program() const; // Returns true if binary program /// Clears all variables, constraints, and the objective. void clear(); /// Solves the program. Returns false if fails. virtual bool solve() = 0; /// Returns the result. /// The result can also be retrieved using Variable::solution_value(). /// \note (1) Result is valid only if the solver succeeded. /// (2) Each entry in the result corresponds to the variable with the /// same index in the program. const std::vector& solution() const { return result_; } /// Returns the error message. /// \note This function should be called after call to solve(). const std::string& error_message() const { return error_message_; } protected: Linear_objective* objective_; std::vector variables_; std::vector constraints_; std::vector result_; std::string error_message_; /// \endcond }; ////////////////////////////////////////////////////////////////////////// // implementation #ifndef DOXYGEN_RUNNING template FT Bound::infinity_ = 1e20; // values larger than this value are considered infinity template Bound::Bound(FT lb /* = -infinity() */, FT ub /* = +infinity() */) : lower_bound_(lb) , upper_bound_(ub) { } template FT Bound::infinity() { return infinity_; } template void Bound::set_bounds(FT lb, FT ub) { lower_bound_ = lb; upper_bound_ = ub; } template void Bound::get_bounds(FT& lb, FT& ub) const { lb = lower_bound_; ub = upper_bound_; } template Variable::Variable( Solver* solver, Variable_type type /* = CONTINUOUS */, FT lb /* = -infinity() */, FT ub /* = +infinity() */, const std::string& name /* = "" */, int idx /* = 0*/ ) : Solver_entry(solver, name, idx) , Bound(lb, ub) , variable_type_(type) , solution_value_(0.0) { if (type == BINARY) Bound::set_bounds(0.0, 1.0); } template void Variable::set_variable_type(Variable_type type) { variable_type_ = type; if (type == BINARY) Bound::set_bounds(0.0, 1.0); } template FT Variable::solution_value(bool rounded /* = false*/) const { if (rounded && variable_type_ != CONTINUOUS) return std::round(solution_value_); else return solution_value_; } ////////////////////////////////////////////////////////////////////////// template Linear_expression::Linear_expression(Solver* solver, const std::string& name, int idx) : Solver_entry(solver, name, idx) , offset_(0.0) { } template void Linear_expression::add_coefficient(const Variable* var, FT coeff) { if (coefficients_.find(var) == coefficients_.end()) coefficients_[var] = coeff; else coefficients_[var] += coeff; } template FT Linear_expression::get_coefficient(const Variable* var) const { typename std::unordered_map::const_iterator pos = coefficients_.find(var); if (pos != coefficients_.end()) return pos->second; else { std::cerr << "linear expression does not own variable " << var->name() << " (" << var->index() << ")" << std::endl; return 0.0; } } template FT Linear_expression::solution_value(bool rounded /* = false*/) const { FT solution = offset_; typename std::unordered_map::const_iterator it = coefficients_.begin(); for (; it != coefficients_.end(); ++it) { const Variable* var = it->first; FT coeff = it->second; solution += var->solution_value(rounded) * coeff; } return solution; } template void Linear_objective::clear() { Linear_expression::clear(); Solver_entry::set_name(""); Solver_entry::set_index(0); } template Linear_constraint::Linear_constraint( Solver* solver, FT lb /* = -infinity() */, FT ub /* = +infinity() */, const std::string& name/* = "" */, int idx /* = 0*/ ) : Linear_expression(solver, name, idx) , Bound(lb, ub) { } template Linear_objective::Linear_objective(Solver* solver, Sense sense) : Linear_expression(solver) , sense_(sense) { } template Mixed_integer_program_traits::Mixed_integer_program_traits() { // Intentionally set the objective to UNDEFINED, so it will allow me to warn // the user if he/she forgot to set the objective sense. objective_ = new Linear_objective(this, Linear_objective::UNDEFINED); } template Mixed_integer_program_traits::~Mixed_integer_program_traits() { clear(); delete objective_; } template void Mixed_integer_program_traits::clear() { for (std::size_t i = 0; i < variables_.size(); ++i) delete variables_[i]; variables_.clear(); for (std::size_t i = 0; i < constraints_.size(); ++i) delete constraints_[i]; constraints_.clear(); objective_->clear(); } namespace internal { /** * Converts an integer v to a string of specified 'width' by * filling with character 'fill' */ template inline std::string from_integer(Int v, int width, char fill) { std::ostringstream string_stream; string_stream << std::setfill(fill) << std::setw(width) << v; return string_stream.str(); } } template typename Mixed_integer_program_traits::Variable* Mixed_integer_program_traits::create_variable( Variable_type type /* = Variable::CONTINUOUS */, FT lb /* = -Variable::infinity() */, FT ub /* = +Variable::infinity() */, const std::string& name /* = "" */) { Variable* v = new Variable(this, type, lb, ub); std::size_t idx = variables_.size(); v->set_index(static_cast(idx)); const std::string& fixed_name = name.empty() ? "x" + internal::from_integer(idx, 9, '0') : name; v->set_name(fixed_name); variables_.push_back(v); return v; } template std::vector::Variable*> Mixed_integer_program_traits::create_variables(std::size_t n) { std::vector variables; for (std::size_t i = 0; i < n; ++i) { Variable* v = create_variable(); variables.push_back(v); } return variables; } template typename Mixed_integer_program_traits::Linear_constraint* Mixed_integer_program_traits::create_constraint( FT lb /* = -Variable::infinity() */, FT ub /* = +Variable::infinity() */, const std::string& name /* = "" */) { Linear_constraint* c = new Linear_constraint(this, lb, ub); std::size_t idx = constraints_.size(); c->set_index(static_cast(idx)); const std::string& fixed_name = name.empty() ? "c" + internal::from_integer(idx, 9, '0') : name; c->set_name(fixed_name); constraints_.push_back(c); return c; } template std::vector::Linear_constraint*> Mixed_integer_program_traits::create_constraints(std::size_t n) { std::vector constraints; for (std::size_t i = 0; i < n; ++i) { Linear_constraint* v = create_constraint(); constraints.push_back(v); } return constraints; } template typename Mixed_integer_program_traits::Linear_objective * Mixed_integer_program_traits::create_objective(Sense sense /* = Linear_objective ::MINIMIZE*/) { if (objective_) delete objective_; objective_ = new Linear_objective(this, sense); return objective_; } template const typename Mixed_integer_program_traits::Linear_objective * Mixed_integer_program_traits::objective() const { if (!objective_) std::cerr << "please call \'create_objective()\' to create an objective first" << std::endl; return objective_; } template typename Mixed_integer_program_traits::Linear_objective * Mixed_integer_program_traits::objective() { if (!objective_) std::cerr << "please call \'create_objective()\' to create an objective first" << std::endl; return objective_; } template std::size_t Mixed_integer_program_traits::number_of_continuous_variables() const { std::size_t num_continuous_var = 0; for (std::size_t i = 0; i < variables_.size(); ++i) { const Variable* v = variables_[i]; if (v->variable_type() == Variable::CONTINUOUS) ++num_continuous_var; } return num_continuous_var; } template std::size_t Mixed_integer_program_traits::number_of_integer_variables() const { std::size_t num_iteger_var = 0; for (std::size_t i = 0; i < variables_.size(); ++i) { const Variable* v = variables_[i]; if (v->variable_type() == Variable::INTEGER) ++num_iteger_var; } return num_iteger_var; } template std::size_t Mixed_integer_program_traits::number_of_binary_variables() const { std::size_t num_binary_var = 0; for (std::size_t i = 0; i < variables_.size(); ++i) { const Variable* v = variables_[i]; if (v->variable_type() == Variable::BINARY) ++num_binary_var; } return num_binary_var; } // Returns true if all variables are continuous template bool Mixed_integer_program_traits::is_continuous() const { std::size_t num = number_of_continuous_variables(); return (num > 0) && (num == variables_.size()); } // Returns true if this is a mixed integer program template bool Mixed_integer_program_traits::is_mixed_integer_program() const { std::size_t num = number_of_continuous_variables(); return (num > 0) && (num < variables_.size()); } // Returns true if inter program template bool Mixed_integer_program_traits::is_integer_program() const { std::size_t num = number_of_integer_variables(); return (num > 0) && (num == variables_.size()); } // Returns true if binary program template bool Mixed_integer_program_traits::is_binary_program() const { std::size_t num = number_of_binary_variables(); return (num > 0) && (num == variables_.size()); } #endif } // namespace CGAL #endif // CGAL_MIXED_INTEGER_PROGRAM_TRAITS_H