// Copyright 2010-2017 Google // 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. #ifndef OR_TOOLS_LINEAR_SOLVER_LINEAR_EXPR_H_ #define OR_TOOLS_LINEAR_SOLVER_LINEAR_EXPR_H_ // This file allows you to write natural code (like a mathematical equation) to // model optimization problems with MPSolver. It is syntatic sugar on top of // the MPSolver API, it provides no additional functionality. Use of these APIs // makes it much easier to write code that is both simple and big-O optimal for // creating your model, at the cost of some additional constant factor // overhead. If model creation is a bottleneck in your problem, consider using // the MPSolver API directly instead. // // This file contains two classes: // 1. LinearExpr: models offset + sum_{i in S} a_i*x_i for decision var x_i, // 2. LinearRange: models lb <= sum_{i in S} a_i*x_i <= ub, // and it provides various operator overloads to build up "LinearExpr"s and // then convert them to "LinearRange"s. // // Recommended use (avoids dangerous code): // // MPSolver solver = ...; // const LinearExpr x = solver.MakeVar(...); // Note: implicit conversion // const LinearExpr y = solver.MakeVar(...); // const LinearExpr z = solver.MakeVar(...); // const LinearExpr e1 = x + y; // const LinearExpr e2 = (e1 + 7.0 + z)/3.0; // const LinearRange r = e1 <= e2; // solver.MakeRowConstraint(r); // // WARNING, AVOID THIS TRAP: // // MPSolver solver = ...; // MPVariable* x = solver.MakeVar(...); // LinearExpr y = x + 5; // // In evaluating "x+5" above, x is NOT converted to a LinearExpr before the // addition, but rather is treated as a pointer, so x+5 gives a new pointer to // garbage memory. // // For this reason, when using LinearExpr, it is best practice to: // 1. use double literals instead of ints (e.g. "x + 5.0", not "x + 5"), // 2. Immediately convert all MPVariable* to LinearExpr on creation, and only // hold references to the "LinearExpr"s. // // Likewise, the following code is NOT recommended: // MPSolver solver = ...; // MPVariable* x = solver.MakeVar(...); // MPVariable* y = solver.MakeVar(...); // LinearExpr e1 = LinearExpr(x) + y + 5; // // While it is correct, it violates the natural assumption that the + operator // is associative. Thus you are setting a trap for future modifications of the // code, as any of the following changes would lead to the above failure mode: // * LinearExpr e1 = LinearExpr(x) + (y + 5); // * LinearExpr e1 = y + 5 + LinearExpr(x); #include namespace operations_research { // NOTE(user): forward declaration is necessary due to cyclic dependencies, // MPVariable is defined in linear_solver.h, which depends on LinearExpr. class MPVariable; // LinearExpr models a quantity that is linear in the decision variables // (MPVariable) of an optimization problem, i.e. // // offset + sum_{i in S} a_i*x_i, // // where the a_i and offset are constants and the x_i are MPVariables. You can // use a LinearExpr "linear_expr" with an MPSolver "solver" to: // * Set as the objective of your optimization problem, e.g. // // solver.MutableObjective()->MaximizeLinearExpr(linear_expr); // // * Create a constraint in your optimization, e.g. // // solver.MakeRowConstraint(linear_expr1 <= linear_expr2); // // * Get the value of the quantity after solving, e.g. // // solver.Solve(); // linear_expr.SolutionValue(); // // LinearExpr is allowed to delete variables with coefficient zero from the map, // but is not obligated to do so. class LinearExpr { public: LinearExpr(); // Possible implicit conversions are intentional. LinearExpr(double constant); // NOLINT // Possible implicit conversions are intentional. // Warning: var is not owned. LinearExpr(const MPVariable* var); // NOLINT // Returns 1-var. // NOTE(user): if var is binary variable, this corresponds to the logical // negation of var. // Passing by value is intentional, see the discussion on binary ops. static LinearExpr NotVar(LinearExpr var); LinearExpr& operator+=(const LinearExpr& rhs); LinearExpr& operator-=(const LinearExpr& rhs); LinearExpr& operator*=(double rhs); LinearExpr& operator/=(double rhs); LinearExpr operator-() const; double offset() const { return offset_; } const std::unordered_map& terms() const { return terms_; } // Call only after calling MPSolver::Solve. Evaluates the value of this // expression at the solution found. double SolutionValue() const; private: double offset_; std::unordered_map terms_; }; // NOTE(user): in the ops below, the non-"const LinearExpr&" are intentional. // We need to create a new LinearExpr for the result, so we lose nothing by // passing one argument by value, mutating it, and then returning it. In // particular, this allows (with move semantics and RVO) an optimized // evaluation of expressions such as // a + b + c + d // (see http://en.cppreference.com/w/cpp/language/operators). LinearExpr operator+(LinearExpr lhs, const LinearExpr& rhs); LinearExpr operator-(LinearExpr lhs, const LinearExpr& rhs); LinearExpr operator*(LinearExpr lhs, double rhs); LinearExpr operator/(LinearExpr lhs, double rhs); LinearExpr operator*(double lhs, LinearExpr rhs); // An expression of the form: // lower_bound <= sum_{i in S} a_i*x_i <= upper_bound. // The sum is represented as a LinearExpr with offset 0. // // Must be added to model with // MPSolver::AddRowConstraint(const LinearRange& range[, const std::string& // name]); class LinearRange { public: LinearRange() : lower_bound_(0), upper_bound_(0) {} // The bounds of the linear range are updated so that they include the offset // from "linear_expr", i.e., we form the range: // lower_bound - offset <= linear_expr - offset <= upper_bound - offset. LinearRange(double lower_bound, const LinearExpr& linear_expr, double upper_bound); double lower_bound() const { return lower_bound_; } const LinearExpr& linear_expr() const { return linear_expr_; } double upper_bound() const { return upper_bound_; } private: double lower_bound_; // invariant: linear_expr_.offset() == 0. LinearExpr linear_expr_; double upper_bound_; }; LinearRange operator<=(const LinearExpr& lhs, const LinearExpr& rhs); LinearRange operator==(const LinearExpr& lhs, const LinearExpr& rhs); LinearRange operator>=(const LinearExpr& lhs, const LinearExpr& rhs); // TODO(user, ondrasej): explore defining more overloads to support: // solver.AddRowConstraint(0.0 <= x + y + z <= 1.0); } // namespace operations_research #endif // OR_TOOLS_LINEAR_SOLVER_LINEAR_EXPR_H_