diff --git a/docs/make.jl b/docs/make.jl index 9d09e9dd54..b6a9ae4209 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -19,9 +19,16 @@ makedocs( doctest = _FIX ? :fix : true, pages = [ "Introduction" => "index.md", + "Background" => [ + "background/motivation.md", + "background/duality.md", + ], "Manual" => [ + "manual/standard_form.md", + "manual/constraints.md", + "manual/status.md", + "manual/modification.md", "manual/basic_usage.md", - "manual/advanced_usage.md", "manual/implementing.md", ], "API Reference" => "reference/reference.md", diff --git a/docs/src/manual/advanced_usage.md b/docs/src/background/duality.md similarity index 97% rename from docs/src/manual/advanced_usage.md rename to docs/src/background/duality.md index 8fc7e59692..34b125e8fe 100644 --- a/docs/src/manual/advanced_usage.md +++ b/docs/src/background/duality.md @@ -7,9 +7,7 @@ end DocTestFilters = [r"MathOptInterface|MOI"] ``` -# Advanced usage - -## Duality +# Duality Conic duality is the starting point for MOI's duality conventions. When all functions are affine (or coordinate projections), and all constraint sets are @@ -109,11 +107,11 @@ and similarly, the dual is: \end{align} ``` -!!! warn +!!! warning For the LP case is that the signs of the feasible duals depend only on the sense of the inequality and not on the objective sense. -### Duality and scalar product +## Duality and scalar product The scalar product is different from the canonical one for the sets [`PositiveSemidefiniteConeTriangle`](@ref), [`LogDetConeTriangle`](@ref), @@ -125,7 +123,7 @@ twice the value of the `coefficients` field in the [`VectorAffineFunction`](@ref for the corresponding rows. See [`PositiveSemidefiniteConeTriangle`](@ref) for details. -### Dual for problems with quadratic functions +## Dual for problems with quadratic functions Given a problem with quadratic functions: ```math diff --git a/docs/src/background/motivation.md b/docs/src/background/motivation.md new file mode 100644 index 0000000000..252341ac07 --- /dev/null +++ b/docs/src/background/motivation.md @@ -0,0 +1,30 @@ +# Motivation + +MOI has been designed to replace [MathProgBase](https://github.com/JuliaOpt/MathProgBase.jl), +which was been used by modeling packages such as [JuMP](https://github.com/jump-dev/JuMP.jl) +and [Convex.jl](https://github.com/jump-dev/Convex.jl). + +This second-generation abstraction layer addresses a number of limitations of +MathProgBase. + +MOI is designed to: +- Be simple and extensible, unifying linear, quadratic, and conic optimization, + and seamlessly facilitate extensions to essentially arbitrary constraints and + functions (e.g., indicator constraints, complementarity constraints, and + piecewise-linear functions) +- Be fast by allowing access to a solver's in-memory representation of a problem + without writing intermediate files (when possible) and by using multiple + dispatch and avoiding requiring containers of nonconcrete types +- Allow a solver to return multiple results (e.g., a pool of solutions) +- Allow a solver to return extra arbitrary information via attributes (e.g., + variable- and constraint-wise membership in an irreducible inconsistent subset + for infeasibility analysis) +- Provide a greatly expanded set of status codes explaining what happened during + the optimization procedure +- Enable a solver to more precisely specify which problem classes it supports +- Enable both primal and dual warm starts +- Enable adding and removing both variables and constraints by indices that are + not required to be consecutive +- Enable any modification that the solver supports to an existing model +- Avoid requiring the solver wrapper to store an additional copy of the problem + data diff --git a/docs/src/index.md b/docs/src/index.md index b6de41b172..2f28e12af1 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -12,15 +12,21 @@ optimization solvers so that users do not need to understand multiple solver-specific APIs. !!! tip - While MOI can be used directly, we encourage you instead to use MOI through - a higher-level modeling interface like [JuMP](https://github.com/jump-dev/JuMP.jl) - or [Convex.jl](https://github.com/jump-dev/Convex.jl). + This documentation is aimed at developers writing software interfaces to + solvers and modeling languages using the MathOptInterface API. If you are a + user interested in solving optimization problems, we encourage you instead + to use MOI through a higher-level modeling interface like + [JuMP](https://github.com/jump-dev/JuMP.jl) or + [Convex.jl](https://github.com/jump-dev/Convex.jl). ## How the documentation is structured Having a high-level overview of how this documentation is structured will help you know where to look for certain things. +* The **Background** section contains articles on the motivation and theory + behind MathOptInterface. Look here if you want to understand _why_, rather + than _how_. * The **Manual** contains short code-snippets that explain how to use the MOI API. Look here if you want to write a model in MOI. * The **API Reference** contains a complete list of functions and types that @@ -30,35 +36,6 @@ you know where to look for certain things. submodules within MOI. These submodules are not required to interface a solver with MOI, but they make the job much easier. -## Background - -MOI has been designed to replace [MathProgBase](https://github.com/JuliaOpt/MathProgBase.jl), -which was been used by modeling packages such as [JuMP](https://github.com/jump-dev/JuMP.jl) -and [Convex.jl](https://github.com/jump-dev/Convex.jl). - -This second-generation abstraction layer addresses a number of limitations of -MathProgBase. MOI is designed to: -- Be simple and extensible, unifying linear, quadratic, and conic optimization, - and seamlessly facilitate extensions to essentially arbitrary constraints and - functions (e.g., indicator constraints, complementarity constraints, and - piecewise-linear functions) -- Be fast by allowing access to a solver's in-memory representation of a problem - without writing intermediate files (when possible) and by using multiple - dispatch and avoiding requiring containers of nonconcrete types -- Allow a solver to return multiple results (e.g., a pool of solutions) -- Allow a solver to return extra arbitrary information via attributes (e.g., - variable- and constraint-wise membership in an irreducible inconsistent subset - for infeasibility analysis) -- Provide a greatly expanded set of status codes explaining what happened during - the optimization procedure -- Enable a solver to more precisely specify which problem classes it supports -- Enable both primal and dual warm starts -- Enable adding and removing both variables and constraints by indices that are - not required to be consecutive -- Enable any modification that the solver supports to an existing model -- Avoid requiring the solver wrapper to store an additional copy of the problem - data - ## Citing JuMP A [paper describing the design and features of MathOptInterface](https://arxiv.org/abs/2002.03447) diff --git a/docs/src/manual/basic_usage.md b/docs/src/manual/basic_usage.md index fa64900908..45d1459e0a 100644 --- a/docs/src/manual/basic_usage.md +++ b/docs/src/manual/basic_usage.md @@ -20,107 +20,7 @@ DocTestFilters = [r"MathOptInterface|MOI"] available in base Julia, we always explicitly use the module prefix, for example, with `MOI.get`. -## Standard form problem - -MathOptInterface represents optimization problems in the standard form: -```math -\begin{align} - & \min_{x \in \mathbb{R}^n} & f_0(x) - \\ - & \;\;\text{s.t.} & f_i(x) & \in \mathcal{S}_i & i = 1 \ldots m -\end{align} -``` -where: -* the functions ``f_0, f_1, \ldots, f_m`` are specified by - [`AbstractFunction`](@ref) objects -* the sets ``\mathcal{S}_1, \ldots, \mathcal{S}_m`` are specified by - [`AbstractSet`](@ref) objects - -### Functions - -MOI defines some commonly used functions, but the interface is extensible to -other sets recognized by the solver. The current function types are: - -* **[`SingleVariable`](@ref)**: ``x_j``, i.e., projection onto a single - coordinate defined by a variable index ``j`` -* **[`VectorOfVariables`](@ref)**: projection onto multiple coordinates (i.e., - extracting a subvector) -* **[`ScalarAffineFunction`](@ref)**: ``a^T x + b``, where ``a`` is a vector and - ``b`` scalar -* **[`VectorAffineFunction`](@ref)**: ``A x + b``, where ``A`` is a matrix and - ``b`` is a vector -* **[`ScalarQuadraticFunction`](@ref)**: ``\frac{1}{2} x^T Q x + a^T x + b``, - where ``Q`` is a symmetric matrix, ``a`` is a vector, and ``b`` is a constant -* **[`VectorQuadraticFunction`](@ref)**: a vector of scalar-valued quadratic - functions - -Extensions for nonlinear programming are present but not yet well documented. - -### Sets - -MOI defines some commonly used sets, but the interface is extensible to other -sets recognized by the solver. The current set types are: - -* **[`LessThan(upper)`](@ref MathOptInterface.LessThan)**: - ``\{ x \in \mathbb{R} : x \le \mbox{upper} \}`` -* **[`GreaterThan(lower)`](@ref MathOptInterface.GreaterThan)**: - ``\{ x \in \mathbb{R} : x \ge \mbox{lower} \}`` -* **[`EqualTo(value)`](@ref MathOptInterface.GreaterThan)**: - ``\{ x \in \mathbb{R} : x = \mbox{value} \}`` -* **[`Interval(lower, upper)`](@ref MathOptInterface.Interval)**: - ``\{ x \in \mathbb{R} : x \in [\mbox{lower},\mbox{upper}] \}`` -* **[`Reals(dimension)`](@ref MathOptInterface.Reals)**: - ``\mathbb{R}^\mbox{dimension}`` -* **[`Zeros(dimension)`](@ref MathOptInterface.Zeros)**: ``0^\mbox{dimension}`` -* **[`Nonnegatives(dimension)`](@ref MathOptInterface.Nonnegatives)**: - ``\{ x \in \mathbb{R}^\mbox{dimension} : x \ge 0 \}`` -* **[`Nonpositives(dimension)`](@ref MathOptInterface.Nonpositives)**: - ``\{ x \in \mathbb{R}^\mbox{dimension} : x \le 0 \}`` -* **[`NormInfinityCone(dimension)`](@ref MathOptInterface.NormInfinityCone)**: - ``\{ (t,x) \in \mathbb{R}^\mbox{dimension} : t \ge \lVert x \rVert_\infty = \max_i \lvert x_i \rvert \}`` -* **[`NormOneCone(dimension)`](@ref MathOptInterface.NormOneCone)**: - ``\{ (t,x) \in \mathbb{R}^\mbox{dimension} : t \ge \lVert x \rVert_1 = \sum_i \lvert x_i \rvert \}`` -* **[`SecondOrderCone(dimension)`](@ref MathOptInterface.SecondOrderCone)**: - ``\{ (t,x) \in \mathbb{R}^\mbox{dimension} : t \ge \lVert x \rVert_2 \}`` -* **[`RotatedSecondOrderCone(dimension)`](@ref MathOptInterface.RotatedSecondOrderCone)**: - ``\{ (t,u,x) \in \mathbb{R}^\mbox{dimension} : 2tu \ge \lVert x \rVert_2^2, t,u \ge 0 \}`` -* **[`GeometricMeanCone(dimension)`](@ref MathOptInterface.GeometricMeanCone)**: - ``\{ (t,x) \in \mathbb{R}^{n+1} : x \ge 0, t \le \sqrt[n]{x_1 x_2 \cdots x_n} \}`` - where ``n`` is ``dimension - 1`` -* **[`ExponentialCone()`](@ref MathOptInterface.ExponentialCone)**: - ``\{ (x,y,z) \in \mathbb{R}^3 : y \exp (x/y) \le z, y > 0 \}`` -* **[`DualExponentialCone()`](@ref MathOptInterface.DualExponentialCone)**: - ``\{ (u,v,w) \in \mathbb{R}^3 : -u \exp (v/u) \le exp(1) w, u < 0 \}`` -* **[`PowerCone(exponent)`](@ref MathOptInterface.PowerCone)**: - ``\{ (x,y,z) \in \mathbb{R}^3 : x^\mbox{exponent} y^{1-\mbox{exponent}} \ge |z|, x,y \ge 0 \}`` -* **[`DualPowerCone(exponent)`](@ref MathOptInterface.DualPowerCone)**: - ``\{ (u,v,w) \in \mathbb{R}^3 : \frac{u}{\mbox{exponent}}^\mbox{exponent}\frac{v}{1-\mbox{exponent}}^{1-\mbox{exponent}} \ge |w|, u,v \ge 0 \}`` -* **[`RelativeEntropyCone(dimension)`](@ref MathOptInterface.RelativeEntropyCone)**: - ``\{ (u, v, w) \in \mathbb{R}^\mbox{dimension} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}`` -* **[`NormSpectralCone(row_dim, column_dim)`](@ref MathOptInterface.NormSpectralCone)**: - ``\{ (t, X) \in \mathbb{R}^{1 + \mbox{row_dim} \times \mbox{column_dim}} : t \ge \sigma_1(X), X \mbox{is a matrix with row_dim rows and column_dim columns} \}`` -* **[`NormNuclearCone(row_dim, column_dim)`](@ref MathOptInterface.NormNuclearCone)**: - ``\{ (t, X) \in \mathbb{R}^{1 + \mbox{row_dim} \times \mbox{column_dim}} : t \ge \sum_i \sigma_i(X), X \mbox{is a matrix with row_dim rows and column_dim columns} \}`` -* **[`PositiveSemidefiniteConeTriangle(dimension)`](@ref MathOptInterface.PositiveSemidefiniteConeTriangle)**: - ``\{ X \in \mathbb{R}^{\mbox{dimension}(\mbox{dimension}+1)/2} : X \mbox{is the upper triangle of a PSD matrix} \}`` -* **[`PositiveSemidefiniteConeSquare(dimension)`](@ref MathOptInterface.PositiveSemidefiniteConeSquare)**: - ``\{ X \in \mathbb{R}^{\mbox{dimension}^2} : X \mbox{is a PSD matrix} \}`` -* **[`LogDetConeTriangle(dimension)`](@ref MathOptInterface.LogDetConeTriangle)**: - ``\{ (t,u,X) \in \mathbb{R}^{2+\mbox{dimension}(1+\mbox{dimension})/2} : t \le u\log(\det(X/u)), X \mbox{is the upper triangle of a PSD matrix}, u > 0 \}`` -* **[`LogDetConeSquare(dimension)`](@ref MathOptInterface.LogDetConeSquare)**: - ``\{ (t,u,X) \in \mathbb{R}^{2+\mbox{dimension}^2} : t \le u \log(\det(X/u)), X \mbox{is a PSD matrix}, u > 0 \}`` -* **[`RootDetConeTriangle(dimension)`](@ref MathOptInterface.RootDetConeTriangle)**: - ``\{ (t,X) \in \mathbb{R}^{1+\mbox{dimension}(1+\mbox{dimension})/2} : t \le det(X)^{1/\mbox{dimension}}, X \mbox{is the upper triangle of a PSD matrix} \}`` -* **[`RootDetConeSquare(dimension)`](@ref MathOptInterface.RootDetConeSquare)**: - ``\{ (t,X) \in \mathbb{R}^{1+\mbox{dimension}^2} : t \le \det(X)^{1/\mbox{dimension}}, X \mbox{is a PSD matrix} \}`` -* **[`Integer()`](@ref MathOptInterface.Integer)**: ``\mathbb{Z}`` -* **[`ZeroOne()`](@ref MathOptInterface.ZeroOne)**: ``\{ 0, 1 \}`` -* **[`Semicontinuous(lower,upper)`](@ref MathOptInterface.Semicontinuous)**: - ``\{ 0\} \cup [lower,upper]`` -* **[`Semiinteger(lower,upper)`](@ref MathOptInterface.Semiinteger)**: - ``\{ 0\} \cup \{lower,lower+1,\ldots,upper-1,upper\}`` - -## The `ModelLike` and `AbstractOptimizer` APIs +## The ModelLike and AbstractOptimizer APIs The most significant part of MOI is the definition of the **model API** that is used to specify an instance of an optimization problem (e.g., by adding @@ -135,21 +35,22 @@ model API, MOI defines [`AbstractOptimizer`](@ref). *Optimizers* (or solvers) implement the model API (inheriting from `ModelLike`) and additionally provide methods to solve the model. -Through the rest of the manual, `model` is used as a generic `ModelLike`, and -`optimizer` is used as a generic `AbstractOptimizer`. +!!! info + Throughout the rest of the manual, `model` is used as a generic `ModelLike`, + and `optimizer` is used as a generic `AbstractOptimizer`. Models are constructed by * adding variables using [`add_variable`](@ref) (or [`add_variables`](@ref)), - see [Adding variables](@ref); + see [Add a variable](@ref); * setting an objective sense and function using [`set`](@ref), see [Setting an objective](@ref); * and adding constraints using [`add_constraint`](@ref) (or - [`add_constraints`](@ref)), see [Sets and Constraints](@ref). + [`add_constraints`](@ref)), see [Constraints](@ref). The way the problem is solved by the optimimizer is controlled by [`AbstractOptimizerAttribute`](@ref)s, see [Solver-specific attributes](@ref). -## Adding variables +## Add a variable All variables in MOI are scalar variables. New scalar variables are created with [`add_variable`](@ref) or [`add_variables`](@ref), which return a [`VariableIndex`](@ref) @@ -158,7 +59,8 @@ type-safe wrappers around integers that refer to a variable in a particular model. !!! note - The integer does not necessarily corresond to the column inside an optimizer! + The integer does not necessarily corresond to the column inside an + optimizer! One uses [`VariableIndex`](@ref) objects to set and get variable attributes. For example, the [`VariablePrimalStart`](@ref) attribute is used to provide an @@ -170,10 +72,14 @@ v2 = MOI.add_variables(model, 3) MOI.set(model, MOI.VariablePrimalStart(), v2, [1.3, 6.8, -4.6]) ``` -A variable can be deleted from a model with +## Delete a variable + +Delete a variable using [`delete(::ModelLike, ::VariableIndex)`](@ref MathOptInterface.delete(::MathOptInterface.ModelLike, ::MathOptInterface.Index)). -Not all models support deleting variables; a [`DeleteNotAllowed`](@ref) error is -thrown if this is not supported. + +!!! warning + Not all `ModelLike` models support deleting variables. A + [`DeleteNotAllowed`](@ref) error is thrown if this is not supported. ## Functions @@ -238,172 +144,6 @@ sets the objective to the function just discussed in the minimization sense. See [Functions and function modifications](@ref) for the complete list of functions. -## Sets and Constraints - -All constraints are specified with [`add_constraint`](@ref) by restricting the -output of some function to a set. The interface allows an arbitrary combination -of functions and sets, but of course solvers may decide to support only a small -number of combinations. - -For example, linear programming solvers should support, at least, combinations -of affine functions with the [`LessThan`](@ref) and [`GreaterThan`](@ref) sets. -These are simply linear constraints. [`SingleVariable`](@ref) functions combined -with these same sets are used to specify upper- and lower-bounds on variables. - -The code example below encodes the linear optimization problem: -```math -\begin{align} -& \max_{x \in \mathbb{R}^2} & 3x_1 + 2x_2 & -\\ -& \;\;\text{s.t.} & x_1 + x_2 &\le 5 -\\ -&& x_1 & \ge 0 -\\ -&&x_2 & \ge -1 -\end{align} -``` - -```julia -x = MOI.add_variables(model, 2) -MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([3.0, 2.0], x), 0.0), -) -MOI.set(model, MOI.ObjectiveSense(), MAX_SENSE) -MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, x), 0.0), - MOI.LessThan(5.0), -) -MOI.add_constraint(model, MOI.SingleVariable(x[1]), MOI.GreaterThan(0.0)) -MOI.add_constraint(model, MOI.SingleVariable(x[2]), MOI.GreaterThan(-1.0)) -``` - -Besides scalar-valued functions in scalar-valued sets, it's also possible to use -vector-valued functions and sets. - -The code example below encodes the convex optimization problem: -```math -\begin{align} -& \max_{x,y,z \in \mathbb{R}} & y + z & -\\ -& \;\;\text{s.t.} & 3x &= 2 -\\ -&& x & \ge \lVert (y,z) \rVert_2 -\end{align} -``` - -```julia -x,y,z = MOI.add_variables(model, 3) -MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, [y, z]), 0.0), -) -MOI.set(model, ObjectiveSense(), MAX_SENSE) -MOI.add_constraint( - model, - MOI.VectorAffineFunction( - [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(3.0, x))], [-2.0] - ), - MOI.Zeros(1), -) -MOI.add_constraint( - model, MOI.VectorOfVariables([x, y, z]), MOI.SecondOrderCone(3) -) -``` - -[TODO Describe ConstraintIndex objects.] - -### Constraints by function-set pairs - -Below is a list of common constraint types and how they are represented -as function-set pairs in MOI. In the notation below, ``x`` is a vector of -decision variables, ``x_i`` is a scalar decision variable, ``\alpha, \beta`` are -scalar constants, ``a, b`` are constant vectors, `A` is a constant matrix and -``\mathbb{R}_+`` (resp. ``\mathbb{R}_-``) is the set of nonnegative (resp. -nonpositive) real numbers. - -#### Linear constraints - -| Mathematical Constraint | MOI Function | MOI Set | -|-------------------------------|------------------------------|----------------| -| ``a^Tx \le \beta`` | `ScalarAffineFunction` | `LessThan` | -| ``a^Tx \ge \alpha`` | `ScalarAffineFunction` | `GreaterThan` | -| ``a^Tx = \beta`` | `ScalarAffineFunction` | `EqualTo` | -| ``\alpha \le a^Tx \le \beta`` | `ScalarAffineFunction` | `Interval` | -| ``x_i \le \beta`` | `SingleVariable` | `LessThan` | -| ``x_i \ge \alpha`` | `SingleVariable` | `GreaterThan` | -| ``x_i = \beta`` | `SingleVariable` | `EqualTo` | -| ``\alpha \le x_i \le \beta`` | `SingleVariable` | `Interval` | -| ``Ax + b \in \mathbb{R}_+^n`` | `VectorAffineFunction` | `Nonnegatives` | -| ``Ax + b \in \mathbb{R}_-^n`` | `VectorAffineFunction` | `Nonpositives` | -| ``Ax + b = 0`` | `VectorAffineFunction` | `Zeros` | - -By convention, solvers are not expected to support nonzero constant terms in the -[`ScalarAffineFunction`](@ref)s the first four rows above, because they are -redundant with the parameters of the sets. For example, ``2x + 1 \le 2`` should -be encoded as ``2x \le 1``. - -Constraints with [`SingleVariable`](@ref) in [`LessThan`](@ref), [`GreaterThan`](@ref), -[`EqualTo`](@ref), or [`Interval`](@ref) sets have a natural interpretation as -variable bounds. As such, it is typically not natural to impose multiple lower- -or upper-bounds on the same variable, and the solver interfaces should throw -respectively [`LowerBoundAlreadySet`](@ref) or [`UpperBoundAlreadySet`](@ref). - -Moreover, adding two [`SingleVariable`](@ref) constraints on the same variable -with the same set is impossible because they share the same index as it is the -index of the variable, see [`ConstraintIndex`](@ref). - -It is natural, however, to impose upper- and lower-bounds separately as two -different constraints on a single variable. The difference between imposing -bounds by using a single [`Interval`](@ref) constraint and by using separate -[`LessThan`](@ref) and [`GreaterThan`](@ref) constraints is that the latter will -allow the solver to return separate dual multipliers for the two bounds, while -the former will allow the solver to return only a single dual for the interval -constraint. - -#### Conic constraints - -| Mathematical Constraint | MOI Function | MOI Set | -|---------------------------------------------------------------|------------------------------|------------------------------------| -| ``\lVert Ax + b\rVert_2 \le c^Tx + d`` | `VectorAffineFunction` | `SecondOrderCone` | -| ``y \ge \lVert x \rVert_2`` | `VectorOfVariables` | `SecondOrderCone` | -| ``2yz \ge \lVert x \rVert_2^2, y,z \ge 0`` | `VectorOfVariables` | `RotatedSecondOrderCone` | -| ``(a_1^Tx + b_1,a_2^Tx + b_2,a_3^Tx + b_3) \in \mathcal{E}`` | `VectorAffineFunction` | `ExponentialCone` | -| ``A(x) \in \mathcal{S}_+`` | `VectorAffineFunction` | `PositiveSemidefiniteConeTriangle` | -| ``B(x) \in \mathcal{S}_+`` | `VectorAffineFunction` | `PositiveSemidefiniteConeSquare` | -| ``x \in \mathcal{S}_+`` | `VectorOfVariables` | `PositiveSemidefiniteConeTriangle` | -| ``x \in \mathcal{S}_+`` | `VectorOfVariables` | `PositiveSemidefiniteConeSquare` | - -where ``\mathcal{E}`` is the exponential cone (see [`ExponentialCone`](@ref)), -``\mathcal{S}_+`` is the set of positive semidefinite symmetric matrices, -``A`` is an affine map that outputs symmetric matrices and -``B`` is an affine map that outputs square matrices. - -#### Quadratic constraints - -| Mathematical Constraint | MOI Function | MOI Set | -|-------------------------------|------------------------------|-------------------------------| -| ``x^TQx + a^Tx + b \ge 0`` | `ScalarQuadraticFunction` | `GreaterThan` | -| ``x^TQx + a^Tx + b \le 0`` | `ScalarQuadraticFunction` | `LessThan` | -| ``x^TQx + a^Tx + b = 0`` | `ScalarQuadraticFunction` | `EqualTo` | -| Bilinear matrix inequality | `VectorQuadraticFunction` | `PositiveSemidefiniteCone...` | - - -#### Discrete and logical constraints - -| Mathematical Constraint | MOI Function | MOI Set | -|--------------------------------------------------------------------------------------------|------------------------|------------------------------------| -| ``x_i \in \mathbb{Z}`` | `SingleVariable` | `Integer` | -| ``x_i \in \{0,1\}`` | `SingleVariable` | `ZeroOne` | -| ``x_i \in \{0\} \cup [l,u]`` | `SingleVariable` | `Semicontinuous` | -| ``x_i \in \{0\} \cup \{l,l+1,\ldots,u-1,u\}`` | `SingleVariable` | `Semiinteger` | -| At most one component of ``x`` can be nonzero | `VectorOfVariables` | `SOS1` | -| At most two components of ``x`` can be nonzero, and if so they must be adjacent components | `VectorOfVariables` | `SOS2` | -| ``y = 1 \implies a^T x \in S`` | `VectorAffineFunction` | `IndicatorSet` | - ## Solving and retrieving the results Once an optimizer is loaded with the objective function and all of the @@ -620,213 +360,3 @@ MOI.get(optimizer, MOI.VariablePrimal(), x) 1.0 ``` -## Problem modification - -In addition to adding and deleting constraints and variables, MathOptInterface -supports modifying, in-place, coefficients in the constraints and the objective -function of a model. These modifications can be grouped into two categories: -modifications which replace the set of function of a constraint with a new set -or function; and modifications which change, in-place, a component of a -function. - -In the following, we detail the various ways this can be -achieved. Readers should note that some solvers will not support problem -modification. - -### Replacements - -First, we discuss how to replace the set or function of a constraint with a new -instance of the same type. - -#### The set of a constraint - -Given a constraint of type `F`-in-`S` (see [Constraints by function-set pairs](@ref) - above for an explanation), we can modify parameters (but not the type) of the - set `S` by replacing it with a new instance of the same type. For example, - given the variable bound ``x \le 1``: -```julia -c = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0)) -``` -we can modify the set so that the bound now ``x \le 2`` as follows: -```julia -MOI.set(model, MOI.ConstraintSet(), c, MOI.LessThan(2.0)) -``` -where `model` is our [`ModelLike`](@ref) model. However, the following will fail -as the new set ([`GreaterThan`](@ref)) is of a different type to the original -set ([`LessThan`](@ref)): -```julia -MOI.set(model, MOI.ConstraintSet(), c, MOI.GreaterThan(2.0)) # errors -``` -If our constraint is an affine inequality, then this corresponds to modifying -the right-hand side of a constraint in linear programming. - -In some special cases, solvers may support efficiently changing the set of a -constraint (for example, from [`LessThan`](@ref) to [`GreaterThan`](@ref)). For -these cases, MathOptInterface provides the [`transform`](@ref) method. For -example, instead of the error we observed above, the following will -work: -```julia -c2 = MOI.transform(model, c, MOI.GreaterThan(1.0)) -``` - -The [`transform`](@ref) function returns a new constraint index, and the old -constraint index (i.e., `c`) is no longer valid: -```julia -MOI.is_valid(model, c) # false -MOI.is_valid(model, c2) # true -``` -Also note that [`transform`](@ref) cannot be called with a set of the same type; -[`set`](@ref) should be used instead. - -#### The function of a constraint - -Given a constraint of type `F`-in-`S` (see [Constraints by function-set pairs](@ref) -above for an explanation), it is also possible to modify the function of type -`F` by replacing it with a new instance of the same type. For example, given the -variable bound ``x \le 1``: -```julia -c = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0)) -``` -we can modify the function so that the bound now ``y \le 1`` as follows: -```julia -MOI.set(model, MOI.ConstraintFunction(), c, MOI.SingleVariable(y)) -``` -where `m` is our [`ModelLike`](@ref) model. However, the following will fail as -the new function is of a different type to the original function: -```julia -MOI.set( - model, - MOI.ConstraintFunction(), - c, - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), -) -``` - -### In-place modification - -The second type of problem modifications allow the user to modify, in-place, the -coefficients of a function. Currently, four modifications are supported by -MathOptInterface. They are: - 1. change the constant term in a scalar function; - 2. change the constant term in a vector function; - 3. change the affine coefficients in a scalar function; and - 4. change the affine coefficients in a vector function. - -To distinguish between the replacement of the function with a new instance -(described above) and the modification of an existing function, the in-place -modifications use the [`modify`](@ref) method: -```julia -MOI.modify(model, index, change::AbstractFunctionModification) -``` -[`modify`](@ref) takes three arguments. The first is the [`ModelLike`](@ref) -model `model`, the second is the constraint index, and the third is an instance -of an [`AbstractFunctionModification`](@ref). - -We now detail each of these four in-place modifications. - -#### Constant term in a scalar function - -MathOptInterface supports is the ability to modify the constant term within a -[`ScalarAffineFunction`](@ref) and a [`ScalarQuadraticFunction`](@ref) using -the [`ScalarConstantChange`](@ref) subtype of -[`AbstractFunctionModification`](@ref). This includes the objective function, as -well as the function in a function-pair constraint. - -For example, consider a problem `model` with the objective ``\max 1.0x + 0.0``: -```julia -MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), -) -``` - -We can modify the constant term in the objective function as follows: -```julia -MOI.modify( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarConstantChange(1.0) -) -``` -The objective function will now be ``\max 1.0x + 1.0``. - -#### Constant terms in a vector function - -We can modify the constant terms in a [`VectorAffineFunction`](@ref) or a -[`VectorQuadraticFunction`](@ref) using the [`VectorConstantChange`](@ref) -subtype of [`AbstractFunctionModification`](@ref). - -For example, consider a model with the following -[`VectorAffineFunction`](@ref)-in-[`Nonpositives`](@ref) constraint: -```julia -c = MOI.add_constraint( - model, - MOI.VectorAffineFunction([ - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)), - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2.0, y)) - ], - [0.0, 0.0], - ), - MOI.Nonpositives(2), -) -``` -We can modify the constant vector in the [`VectorAffineFunction`](@ref) from -`[0.0, 0.0]` to `[1.0, 2.0]` as follows: -```julia -MOI.modify(model, c, MOI.VectorConstantChange([1.0, 2.0]) -) -``` -The constraints are now ``1.0x + 1.0 \le 0.0`` and ``2.0y + 2.0 \le 0.0``. - -#### Affine coefficients in a scalar function - -In addition to modifying the constant terms in a function, we can also modify -the affine variable coefficients in an [`ScalarAffineFunction`](@ref) or a -[`ScalarQuadraticFunction`](@ref) using the [`ScalarCoefficientChange`](@ref) -subtype of [`AbstractFunctionModification`](@ref). - -For example, given the constraint ``1.0x <= 1.0``: -```julia -c = MOI.add_constraint( - model, - MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), - MOI.LessThan(1.0), -) -``` -we can modify the coefficient of the `x` variable so that the constraint becomes -``2.0x <= 1.0`` as follows: -```julia -MOI.modify(model, c, MOI.ScalarCoefficientChange(x, 2.0)) -``` - -[`ScalarCoefficientChange`](@ref) can also be used to modify the objective -function by passing an instance of [`ObjectiveFunction`](@ref) instead of the -constraint index `c` as we saw above. - -#### Affine coefficients in a vector function - -Finally, the last modification supported by MathOptInterface is the ability to -modify the affine coefficients of a single variable in a -[`VectorAffineFunction`](@ref) or a [`VectorQuadraticFunction`](@ref) using -the [`MultirowChange`](@ref) subtype of [`AbstractFunctionModification`](@ref). - -For example, given the constraint ``Ax \in \mathbb{R}^2_+``, where -``A = [1.0, 2.0]^\top``: -```julia -c = MOI.add_constraint( - model, - MOI.VectorAffineFunction([ - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)), - MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(2.0, x)), - ], - [0.0, 0.0], - ), - MOI.Nonnegatives(2), -) -``` -we can modify the coefficients of the `x` variable so that the `A` matrix -becomes ``A = [3.0, 4.0]^\top`` as follows: -```julia -MOI.modify(model, c, MOI.MultirowChange(x, [3.0, 4.0])) -``` diff --git a/docs/src/manual/constraints.md b/docs/src/manual/constraints.md new file mode 100644 index 0000000000..eccd33d5d8 --- /dev/null +++ b/docs/src/manual/constraints.md @@ -0,0 +1,174 @@ +```@meta +CurrentModule = MathOptInterface +DocTestSetup = quote + using MathOptInterface + const MOI = MathOptInterface +end +DocTestFilters = [r"MathOptInterface|MOI"] +``` + +# Constraints + +All constraints are specified with [`add_constraint`](@ref) by restricting the +output of some function to a set. The interface allows an arbitrary combination +of functions and sets, but of course solvers may decide to support only a small +number of combinations. + +For example, linear programming solvers should support, at least, combinations +of affine functions with the [`LessThan`](@ref) and [`GreaterThan`](@ref) sets. +These are simply linear constraints. [`SingleVariable`](@ref) functions combined +with these same sets are used to specify upper- and lower-bounds on variables. + +The code example below encodes the linear optimization problem: +```math +\begin{align} +& \max_{x \in \mathbb{R}^2} & 3x_1 + 2x_2 & +\\ +& \;\;\text{s.t.} & x_1 + x_2 &\le 5 +\\ +&& x_1 & \ge 0 +\\ +&&x_2 & \ge -1 +\end{align} +``` + +```julia +x = MOI.add_variables(model, 2) +MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([3.0, 2.0], x), 0.0), +) +MOI.set(model, MOI.ObjectiveSense(), MAX_SENSE) +MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, x), 0.0), + MOI.LessThan(5.0), +) +MOI.add_constraint(model, MOI.SingleVariable(x[1]), MOI.GreaterThan(0.0)) +MOI.add_constraint(model, MOI.SingleVariable(x[2]), MOI.GreaterThan(-1.0)) +``` + +Besides scalar-valued functions in scalar-valued sets, it's also possible to use +vector-valued functions and sets. + +The code example below encodes the convex optimization problem: +```math +\begin{align} +& \max_{x,y,z \in \mathbb{R}} & y + z & +\\ +& \;\;\text{s.t.} & 3x &= 2 +\\ +&& x & \ge \lVert (y,z) \rVert_2 +\end{align} +``` + +```julia +x,y,z = MOI.add_variables(model, 3) +MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(1.0, [y, z]), 0.0), +) +MOI.set(model, ObjectiveSense(), MAX_SENSE) +MOI.add_constraint( + model, + MOI.VectorAffineFunction( + [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(3.0, x))], [-2.0] + ), + MOI.Zeros(1), +) +MOI.add_constraint( + model, MOI.VectorOfVariables([x, y, z]), MOI.SecondOrderCone(3) +) +``` + +[TODO Describe ConstraintIndex objects.] + +### Constraints by function-set pairs + +Below is a list of common constraint types and how they are represented +as function-set pairs in MOI. In the notation below, ``x`` is a vector of +decision variables, ``x_i`` is a scalar decision variable, ``\alpha, \beta`` are +scalar constants, ``a, b`` are constant vectors, `A` is a constant matrix and +``\mathbb{R}_+`` (resp. ``\mathbb{R}_-``) is the set of nonnegative (resp. +nonpositive) real numbers. + +#### Linear constraints + +| Mathematical Constraint | MOI Function | MOI Set | +|-------------------------------|------------------------------|----------------| +| ``a^Tx \le \beta`` | `ScalarAffineFunction` | `LessThan` | +| ``a^Tx \ge \alpha`` | `ScalarAffineFunction` | `GreaterThan` | +| ``a^Tx = \beta`` | `ScalarAffineFunction` | `EqualTo` | +| ``\alpha \le a^Tx \le \beta`` | `ScalarAffineFunction` | `Interval` | +| ``x_i \le \beta`` | `SingleVariable` | `LessThan` | +| ``x_i \ge \alpha`` | `SingleVariable` | `GreaterThan` | +| ``x_i = \beta`` | `SingleVariable` | `EqualTo` | +| ``\alpha \le x_i \le \beta`` | `SingleVariable` | `Interval` | +| ``Ax + b \in \mathbb{R}_+^n`` | `VectorAffineFunction` | `Nonnegatives` | +| ``Ax + b \in \mathbb{R}_-^n`` | `VectorAffineFunction` | `Nonpositives` | +| ``Ax + b = 0`` | `VectorAffineFunction` | `Zeros` | + +By convention, solvers are not expected to support nonzero constant terms in the +[`ScalarAffineFunction`](@ref)s the first four rows above, because they are +redundant with the parameters of the sets. For example, ``2x + 1 \le 2`` should +be encoded as ``2x \le 1``. + +Constraints with [`SingleVariable`](@ref) in [`LessThan`](@ref), [`GreaterThan`](@ref), +[`EqualTo`](@ref), or [`Interval`](@ref) sets have a natural interpretation as +variable bounds. As such, it is typically not natural to impose multiple lower- +or upper-bounds on the same variable, and the solver interfaces should throw +respectively [`LowerBoundAlreadySet`](@ref) or [`UpperBoundAlreadySet`](@ref). + +Moreover, adding two [`SingleVariable`](@ref) constraints on the same variable +with the same set is impossible because they share the same index as it is the +index of the variable, see [`ConstraintIndex`](@ref). + +It is natural, however, to impose upper- and lower-bounds separately as two +different constraints on a single variable. The difference between imposing +bounds by using a single [`Interval`](@ref) constraint and by using separate +[`LessThan`](@ref) and [`GreaterThan`](@ref) constraints is that the latter will +allow the solver to return separate dual multipliers for the two bounds, while +the former will allow the solver to return only a single dual for the interval +constraint. + +#### Conic constraints + +| Mathematical Constraint | MOI Function | MOI Set | +|---------------------------------------------------------------|------------------------------|------------------------------------| +| ``\lVert Ax + b\rVert_2 \le c^Tx + d`` | `VectorAffineFunction` | `SecondOrderCone` | +| ``y \ge \lVert x \rVert_2`` | `VectorOfVariables` | `SecondOrderCone` | +| ``2yz \ge \lVert x \rVert_2^2, y,z \ge 0`` | `VectorOfVariables` | `RotatedSecondOrderCone` | +| ``(a_1^Tx + b_1,a_2^Tx + b_2,a_3^Tx + b_3) \in \mathcal{E}`` | `VectorAffineFunction` | `ExponentialCone` | +| ``A(x) \in \mathcal{S}_+`` | `VectorAffineFunction` | `PositiveSemidefiniteConeTriangle` | +| ``B(x) \in \mathcal{S}_+`` | `VectorAffineFunction` | `PositiveSemidefiniteConeSquare` | +| ``x \in \mathcal{S}_+`` | `VectorOfVariables` | `PositiveSemidefiniteConeTriangle` | +| ``x \in \mathcal{S}_+`` | `VectorOfVariables` | `PositiveSemidefiniteConeSquare` | + +where ``\mathcal{E}`` is the exponential cone (see [`ExponentialCone`](@ref)), +``\mathcal{S}_+`` is the set of positive semidefinite symmetric matrices, +``A`` is an affine map that outputs symmetric matrices and +``B`` is an affine map that outputs square matrices. + +#### Quadratic constraints + +| Mathematical Constraint | MOI Function | MOI Set | +|-------------------------------|------------------------------|-------------------------------| +| ``x^TQx + a^Tx + b \ge 0`` | `ScalarQuadraticFunction` | `GreaterThan` | +| ``x^TQx + a^Tx + b \le 0`` | `ScalarQuadraticFunction` | `LessThan` | +| ``x^TQx + a^Tx + b = 0`` | `ScalarQuadraticFunction` | `EqualTo` | +| Bilinear matrix inequality | `VectorQuadraticFunction` | `PositiveSemidefiniteCone...` | + + +#### Discrete and logical constraints + +| Mathematical Constraint | MOI Function | MOI Set | +|--------------------------------------------------------------------------------------------|------------------------|------------------------------------| +| ``x_i \in \mathbb{Z}`` | `SingleVariable` | `Integer` | +| ``x_i \in \{0,1\}`` | `SingleVariable` | `ZeroOne` | +| ``x_i \in \{0\} \cup [l,u]`` | `SingleVariable` | `Semicontinuous` | +| ``x_i \in \{0\} \cup \{l,l+1,\ldots,u-1,u\}`` | `SingleVariable` | `Semiinteger` | +| At most one component of ``x`` can be nonzero | `VectorOfVariables` | `SOS1` | +| At most two components of ``x`` can be nonzero, and if so they must be adjacent components | `VectorOfVariables` | `SOS2` | +| ``y = 1 \implies a^T x \in S`` | `VectorAffineFunction` | `IndicatorSet` | diff --git a/docs/src/manual/modification.md b/docs/src/manual/modification.md new file mode 100644 index 0000000000..b28999c3dc --- /dev/null +++ b/docs/src/manual/modification.md @@ -0,0 +1,246 @@ +```@meta +CurrentModule = MathOptInterface +DocTestSetup = quote + using MathOptInterface + const MOI = MathOptInterface +end +DocTestFilters = [r"MathOptInterface|MOI"] +``` + +# Problem modification + +In addition to adding and deleting constraints and variables, MathOptInterface +supports modifying, in-place, coefficients in the constraints and the objective +function of a model. + +These modifications can be grouped into two categories: + * modifications which replace the set of function of a constraint with a new + set or function + * modifications which change, in-place, a component of a function + +!!! warning + Solve `ModelLike` objects do not support problem modification. + +## Modify the set of a constraint + +Use [`set`](@ref) and [`ConstraintSet`](@ref) to modify the set of a constraint +by replacing it with a new instance of the same type. + +```jldoctest modify_set; setup=:(model = MOI.Utilities.Model{Float64}(); x = MOI.add_variable(model)) +julia> c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), + MOI.EqualTo(1.0), + ) +MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.EqualTo{Float64}}(1) + +julia> MOI.set(model, MOI.ConstraintSet(), c, MOI.EqualTo(2.0)); + +julia> MOI.get(model, MOI.ConstraintSet(), c) == MOI.EqualTo(2.0) +true +``` + + +However, the following will fail as the new set is of a different type to the +original set: +```julia +julia> MOI.set(model, MOI.ConstraintSet(), c, MOI.GreaterThan(2.0)) +ERROR: [...] +``` + +### Special cases: set transforms + +If our constraint is an affine inequality, then this corresponds to modifying +the right-hand side of a constraint in linear programming. + +In some special cases, solvers may support efficiently changing the set of a +constraint (for example, from [`LessThan`](@ref) to [`GreaterThan`](@ref)). For +these cases, MathOptInterface provides the [`transform`](@ref) method. + +The [`transform`](@ref) function returns a new constraint index, and the old +constraint index (i.e., `c`) is no longer valid. + +```jldoctest transform_set; setup=:(model = MOI.Utilities.Model{Float64}(); x = MOI.add_variable(model)) +julia> c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), + MOI.LessThan(1.0), + ) +MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.LessThan{Float64}}(1) + +julia> new_c = MOI.transform(model, c, MOI.GreaterThan(2.0)); + +julia> MOI.is_valid(model, c) +false + +julia> MOI.is_valid(model, new_c) +true +``` + +!!! note + [`transform`](@ref) cannot be called with a set of the same type. Use + [`set`](@ref) instead. + +## Modify the function of a constraint + +Use [`set`](@ref) and [`ConstraintFunction`](@ref) to modify the function of a +constraint by replacing it with a new instance of the same type. + +```jldoctest modify_function; setup=:(model = MOI.Utilities.Model{Float64}(); x = MOI.add_variable(model)) +julia> c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), + MOI.EqualTo(1.0), + ) +MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.EqualTo{Float64}}(1) + +julia> new_f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(2.0, x)], 1.0); + +julia> MOI.set(model, MOI.ConstraintFunction(), c, new_f); + +julia> MOI.get(model, MOI.ConstraintFunction(), c) ≈ new_f +true +``` + +However, the following will fail as the new function is of a different type to +the original function: +```julia +julia> MOI.set(model, MOI.ConstraintFunction(), c, MOI.SingleVariable(x)) +ERROR: [...] +``` + +## Modify constant term in a scalar function + +Use [`modify`](@ref) and [`ScalarConstantChange`](@ref) to modify the constant +term in a [`ScalarAffineFunction`](@ref) or [`ScalarQuadraticFunction`](@ref). + +```jldoctest scalar_constant_change; setup=:(model = MOI.Utilities.Model{Float64}(); x = MOI.add_variable(model)) +julia> c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), + MOI.EqualTo(1.0), + ) +MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.EqualTo{Float64}}(1) + +julia> MOI.modify(model, c, MOI.ScalarConstantChange(1.0)); + +julia> new_f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 1.0); + +julia> MOI.get(model, MOI.ConstraintFunction(), c) ≈ new_f +true +``` + +!!! tip + [`ScalarConstantChange`](@ref) can also be used to modify the objective + function by passing an instance of [`ObjectiveFunction`](@ref) instead of + the constraint index `c` as we saw above. + +```jldoctest scalar_constant_change +julia> MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + new_f, + ); + +julia> MOI.modify( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarConstantChange(-1.0) + ); + +julia> MOI.get( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + ) ≈ MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], -1.0) +true +``` + +## Modify constant terms in a vector function + +Use [`modify`](@ref) and [`VectorConstantChange`](@ref) to modify the constant +vector in a [`VectorAffineFunction`](@ref) or [`VectorQuadraticFunction`](@ref). + +```jldoctest vector_constant_change; setup=:(model = MOI.Utilities.Model{Float64}(); x = MOI.add_variable(model)) +julia> c = MOI.add_constraint( + model, + MOI.VectorAffineFunction([ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(2.0, x)), + ], + [0.0, 0.0], + ), + MOI.Nonnegatives(2), + ) +MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64},MathOptInterface.Nonnegatives}(1) + +julia> MOI.modify(model, c, MOI.VectorConstantChange([3.0, 4.0])); + +julia> new_f = MOI.VectorAffineFunction( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(2.0, x)), + ], + [3.0, 4.0], + ); + +julia> MOI.get(model, MOI.ConstraintFunction(), c) ≈ new_f +true +``` + +## Modify affine coefficients in a scalar function + +Use [`modify`](@ref) and [`ScalarCoefficientChange`](@ref) to modify the affine +coefficient of a [`ScalarAffineFunction`](@ref) or [`ScalarQuadraticFunction`](@ref). + +```jldoctest scalar_coefficient_change; setup=:(model = MOI.Utilities.Model{Float64}(); x = MOI.add_variable(model)) +julia> c = MOI.add_constraint( + model, + MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0), + MOI.EqualTo(1.0), + ) +MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.EqualTo{Float64}}(1) + +julia> MOI.modify(model, c, MOI.ScalarCoefficientChange(x, 2.0)); + +julia> new_f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(2.0, x)], 0.0); + +julia> MOI.get(model, MOI.ConstraintFunction(), c) ≈ new_f +true +``` + +!!! tip + [`ScalarCoefficientChange`](@ref) can also be used to modify the objective + function by passing an instance of [`ObjectiveFunction`](@ref) instead of + the constraint index `c` as we saw above. + +## Modify affine coefficients in a vector function + +Use [`modify`](@ref) and [`MultirowChange`](@ref) to modify a vector of affine +coefficients in a [`VectorAffineFunction`](@ref) or a [`VectorQuadraticFunction`](@ref). + +```jldoctest multirow_change; setup=:(model = MOI.Utilities.Model{Float64}(); x = MOI.add_variable(model)) +julia> c = MOI.add_constraint( + model, + MOI.VectorAffineFunction([ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(2.0, x)), + ], + [0.0, 0.0], + ), + MOI.Nonnegatives(2), + ) +MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64},MathOptInterface.Nonnegatives}(1) + +julia> MOI.modify(model, c, MOI.MultirowChange(x, [(1, 3.0), (2, 4.0)])); + +julia> new_f = MOI.VectorAffineFunction( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(3.0, x)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(4.0, x)), + ], + [0.0, 0.0], + ); + +julia> MOI.get(model, MOI.ConstraintFunction(), c) ≈ new_f +true +``` diff --git a/docs/src/manual/standard_form.md b/docs/src/manual/standard_form.md new file mode 100644 index 0000000000..e4600da18f --- /dev/null +++ b/docs/src/manual/standard_form.md @@ -0,0 +1,155 @@ +```@meta +CurrentModule = MathOptInterface +DocTestSetup = quote + using MathOptInterface + const MOI = MathOptInterface +end +DocTestFilters = [r"MathOptInterface|MOI"] +``` + +# Standard form problem + +MathOptInterface represents optimization problems in the standard form: +```math +\begin{align} + & \min_{x \in \mathbb{R}^n} & f_0(x) + \\ + & \;\;\text{s.t.} & f_i(x) & \in \mathcal{S}_i & i = 1 \ldots m +\end{align} +``` +where: +* the functions ``f_0, f_1, \ldots, f_m`` are specified by + [`AbstractFunction`](@ref) objects +* the sets ``\mathcal{S}_1, \ldots, \mathcal{S}_m`` are specified by + [`AbstractSet`](@ref) objects + +!!! tip + For more information on this standard form, read [our paper](https://arxiv.org/pdf/2002.03447.pdf). + +MOI defines some commonly used functions and sets, but the interface is +extensible to other sets recognized by the solver. + +## Functions + +The function types implemented in MathOptInterface.jl are: + +* [`SingleVariable`](@ref): ``x_j``, i.e., projection onto a single coordinate + defined by a variable index ``j``. +* [`VectorOfVariables`](@ref): projection onto multiple coordinates (i.e., + extracting a subvector). +* [`ScalarAffineFunction`](@ref): ``a^T x + b``, where ``a`` is a vector and + ``b`` scalar. +* [`VectorAffineFunction`](@ref): ``A x + b``, where ``A`` is a matrix and + ``b`` is a vector. +* [`ScalarQuadraticFunction`](@ref): ``\frac{1}{2} x^T Q x + a^T x + b``, + where ``Q`` is a symmetric matrix, ``a`` is a vector, and ``b`` is a constant. +* [`VectorQuadraticFunction`](@ref): a vector of scalar-valued quadratic + functions. + +Extensions for nonlinear programming are present but not yet well documented. + +## One-dimensional sets + +The one-dimensional set types implemented in MathOptInterface.jl are: + +* [`LessThan(upper)`](@ref MathOptInterface.LessThan): + ``\{ x \in \mathbb{R} : x \le \mbox{upper} \}`` +* [`GreaterThan(lower)`](@ref MathOptInterface.GreaterThan): + ``\{ x \in \mathbb{R} : x \ge \mbox{lower} \}`` +* [`EqualTo(value)`](@ref MathOptInterface.GreaterThan): + ``\{ x \in \mathbb{R} : x = \mbox{value} \}`` +* [`Interval(lower, upper)`](@ref MathOptInterface.Interval): + ``\{ x \in \mathbb{R} : x \in [\mbox{lower},\mbox{upper}] \}`` +* [`Integer()`](@ref MathOptInterface.Integer): ``\mathbb{Z}`` +* [`ZeroOne()`](@ref MathOptInterface.ZeroOne): ``\{ 0, 1 \}`` +* [`Semicontinuous(lower,upper)`](@ref MathOptInterface.Semicontinuous): + ``\{ 0\} \cup [lower,upper]`` +* [`Semiinteger(lower,upper)`](@ref MathOptInterface.Semiinteger): + ``\{ 0\} \cup \{lower,lower+1,\ldots,upper-1,upper\}`` + +## Vector cones + +The vector-valued set types implemented in MathOptInterface.jl are: + +* [`Reals(dimension)`](@ref MathOptInterface.Reals): + ``\mathbb{R}^\mbox{dimension}`` +* [`Zeros(dimension)`](@ref MathOptInterface.Zeros): ``0^\mbox{dimension}`` +* [`Nonnegatives(dimension)`](@ref MathOptInterface.Nonnegatives): + ``\{ x \in \mathbb{R}^\mbox{dimension} : x \ge 0 \}`` +* [`Nonpositives(dimension)`](@ref MathOptInterface.Nonpositives): + ``\{ x \in \mathbb{R}^\mbox{dimension} : x \le 0 \}`` +* [`SecondOrderCone(dimension)`](@ref MathOptInterface.SecondOrderCone): + ``\{ (t,x) \in \mathbb{R}^\mbox{dimension} : t \ge \lVert x \rVert_2 \}`` +* [`RotatedSecondOrderCone(dimension)`](@ref MathOptInterface.RotatedSecondOrderCone): + ``\{ (t,u,x) \in \mathbb{R}^\mbox{dimension} : 2tu \ge \lVert x \rVert_2^2, t,u \ge 0 \}`` +* [`ExponentialCone()`](@ref MathOptInterface.ExponentialCone): + ``\{ (x,y,z) \in \mathbb{R}^3 : y \exp (x/y) \le z, y > 0 \}`` +* [`DualExponentialCone()`](@ref MathOptInterface.DualExponentialCone): + ``\{ (u,v,w) \in \mathbb{R}^3 : -u \exp (v/u) \le exp(1) w, u < 0 \}`` +* [`GeometricMeanCone(dimension)`](@ref MathOptInterface.GeometricMeanCone): + ``\{ (t,x) \in \mathbb{R}^{n+1} : x \ge 0, t \le \sqrt[n]{x_1 x_2 \cdots x_n} \}`` + where ``n`` is ``dimension - 1`` +* [`PowerCone(exponent)`](@ref MathOptInterface.PowerCone): + ``\{ (x,y,z) \in \mathbb{R}^3 : x^\mbox{exponent} y^{1-\mbox{exponent}} \ge |z|, x,y \ge 0 \}`` +* [`DualPowerCone(exponent)`](@ref MathOptInterface.DualPowerCone): + ``\{ (u,v,w) \in \mathbb{R}^3 : \frac{u}{\mbox{exponent}}^\mbox{exponent}\frac{v}{1-\mbox{exponent}}^{1-\mbox{exponent}} \ge |w|, u,v \ge 0 \}`` +* [`NormOneCone(dimension)`](@ref MathOptInterface.NormOneCone): +``\{ (t,x) \in \mathbb{R}^\mbox{dimension} : t \ge \lVert x \rVert_1 = \sum_i \lvert x_i \rvert \}`` +* [`NormInfinityCone(dimension)`](@ref MathOptInterface.NormInfinityCone): + ``\{ (t,x) \in \mathbb{R}^\mbox{dimension} : t \ge \lVert x \rVert_\infty = \max_i \lvert x_i \rvert \}`` +* [`RelativeEntropyCone(dimension)`](@ref MathOptInterface.RelativeEntropyCone): + ``\{ (u, v, w) \in \mathbb{R}^\mbox{dimension} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}`` + +## Matrix cones + +The matrix-valued set types implemented in MathOptInterface.jl are: + +* [`RootDetConeTriangle(dimension)`](@ref MathOptInterface.RootDetConeTriangle): + ``\{ (t,X) \in \mathbb{R}^{1+\mbox{dimension}(1+\mbox{dimension})/2} : t \le det(X)^{1/\mbox{dimension}}, X \mbox{is the upper triangle of a PSD matrix} \}`` +* [`RootDetConeSquare(dimension)`](@ref MathOptInterface.RootDetConeSquare): + ``\{ (t,X) \in \mathbb{R}^{1+\mbox{dimension}^2} : t \le \det(X)^{1/\mbox{dimension}}, X \mbox{is a PSD matrix} \}`` + +* [`PositiveSemidefiniteConeTriangle(dimension)`](@ref MathOptInterface.PositiveSemidefiniteConeTriangle): + ``\{ X \in \mathbb{R}^{\mbox{dimension}(\mbox{dimension}+1)/2} : X \mbox{is the upper triangle of a PSD matrix} \}`` +* [`PositiveSemidefiniteConeSquare(dimension)`](@ref MathOptInterface.PositiveSemidefiniteConeSquare): + ``\{ X \in \mathbb{R}^{\mbox{dimension}^2} : X \mbox{is a PSD matrix} \}`` + +* [`LogDetConeTriangle(dimension)`](@ref MathOptInterface.LogDetConeTriangle): + ``\{ (t,u,X) \in \mathbb{R}^{2+\mbox{dimension}(1+\mbox{dimension})/2} : t \le u\log(\det(X/u)), X \mbox{is the upper triangle of a PSD matrix}, u > 0 \}`` +* [`LogDetConeSquare(dimension)`](@ref MathOptInterface.LogDetConeSquare): + ``\{ (t,u,X) \in \mathbb{R}^{2+\mbox{dimension}^2} : t \le u \log(\det(X/u)), X \mbox{is a PSD matrix}, u > 0 \}`` + +* [`NormSpectralCone(row_dim, column_dim)`](@ref MathOptInterface.NormSpectralCone): + ``\{ (t, X) \in \mathbb{R}^{1 + \mbox{row_dim} \times \mbox{column_dim}} : t \ge \sigma_1(X), X \mbox{is a matrix with row_dim rows and column_dim columns} \}`` +* [`NormNuclearCone(row_dim, column_dim)`](@ref MathOptInterface.NormNuclearCone): + ``\{ (t, X) \in \mathbb{R}^{1 + \mbox{row_dim} \times \mbox{column_dim}} : t \ge \sum_i \sigma_i(X), X \mbox{is a matrix with row_dim rows and column_dim columns} \}`` + +Some of these cones can take two forms: `XXXConeTriangle` and `XXXConeSquare`. + +In `XXXConeTriangle` sets, the matrix is assumed to be symmetric, and the +elements are provided by a vector, in which the entries of the upper-right +triangular part of the matrix are given column by column (or equivalently, the +entries of the lower-left triangular part are given row by row). + +In `XXXConeSquare` sets, the entries of the matrix are given column by column +(or equivalently, row by row), and the matrix is constrained to be symmetric. As +an example, given a 2-by-2 matrix of variables `X` and a one-dimensional +variable `t`, we can specify a root-det constraint as +`[t, X11, X12, X22] ∈ RootDetConeTriangle` or +`[t, X11, X12, X21, X22] ∈ RootDetConeSquare`. + +We provide both forms to enable flexibility for solvers who may natively support +one or the other. Transformations between `XXXConeTriangle` and `XXXConeSquare` +are handled by bridges, which removes the chance of conversion mistakes by users +or solver developers. + +## Multi-dimensional sets with combinatorial structure + +* [`SOS1(weights)`](@ref MathOptInterface.SOS1): + A special ordered set of Type I. +* [`SOS2(weights)`](@ref MathOptInterface.SOS2): + A special ordered set of Type II. +* [`IndicatorSet(set)`](@ref MathOptInterface.IndicatorSet): + A set to specify indicator constraints. +* [`Complements`](@ref MathOptInterface.Complements): + A set for mixed complementarity constraints. diff --git a/docs/src/manual/status.md b/docs/src/manual/status.md new file mode 100644 index 0000000000..6cdeb63272 --- /dev/null +++ b/docs/src/manual/status.md @@ -0,0 +1,125 @@ +```@meta +CurrentModule = MathOptInterface +DocTestSetup = quote + using MathOptInterface + const MOI = MathOptInterface +end +DocTestFilters = [r"MathOptInterface|MOI"] +``` + +# Statuses + +## Why did the solver stop? + +The optimization procedure may terminate for a number of reasons. The +[`TerminationStatus`](@ref) attribute of the optimizer returns a +[`TerminationStatusCode`](@ref) object which explains why the solver stopped. + +The termination statuses distinguish between proofs of optimality, +infeasibility, local convergence, limits, and termination because of something +unexpected like invalid problem data or failure to converge. + +A typical usage of the [`TerminationStatus`](@ref) attribute is as follows: +```julia +status = MOI.get(optimizer, TerminationStatus()) +if status == MOI.OPTIMAL + # Ok, we solved the problem! +else + # Handle other cases. +end +``` + +After checking the [`TerminationStatus`](@ref), one should typically check +[`ResultCount`](@ref). This attribute returns the number of results that the +solver has available to return. *A result is defined as a primal-dual pair, +but either the primal or the dual may be missing from the result.* While the +`OPTIMAL` termination status normally implies that at least one result is +available, other statuses do not. For example, in the case of infeasiblity, a +solver may return no result or a proof of infeasibility. The [`ResultCount`](@ref) +attribute distinguishes between these two cases. + +## What solution did the solver return? + +The [`PrimalStatus`](@ref) and [`DualStatus`](@ref) attributes return a +[`ResultStatusCode`](@ref) that indicates if that component of the result +is present (i.e., not `NO_SOLUTION`) and explains how to interpret the result. + +If `PrimalStatus` is not `NO_SOLUTION`, then the primal may be retrieved with +the [`VariablePrimal`](@ref) attribute: +```julia +MOI.get(optimizer, VariablePrimal(), x) +``` + +If `x` is a [`VariableIndex`](@ref) then the function call returns a scalar, and +if `x` is a `Vector{VariableIndex}` then the call returns a vector of scalars. +`VariablePrimal()` is equivalent to `VariablePrimal(1)`, i.e., the variable +primal vector of the first result. Use `VariablePrimal(N)` to access the `N`th +result. + +See also the attributes [`ConstraintPrimal`](@ref), and [`ConstraintDual`](@ref). + +See [Duality](@ref) for a discussion of the MOI conventions for primal-dual +pairs and certificates. + +!!! note + We omit discussion of how to handle multiple results, i.e., when + `ResultCount` is greater than 1. This is supported in the API but not yet + implemented in any solver. + +## Common status situations + +The sections below describe how to interpret typical or interesting status cases +for three common classes of solvers. The example cases are illustrative, not +comprehensive. Solver wrappers may provide additional information on +how the solver's statuses map to MOI statuses. + +!!! info + `*` in the tables indicate that multiple different values are possible. + +### Primal-dual convex solver + +Linear programming and conic optimization solvers fall into this category. + +| What happened? | `TerminationStatus` | `ResultCount` | `PrimalStatus` | `DualStatus` | +| --------------------------------------- | --------------------- | ----------- | --------------------------- | --------------------------- | +| Proved optimality | `OPTIMAL` | 1 | `FEASIBLE_POINT` | `FEASIBLE_POINT` | +| Proved infeasible | `INFEASIBLE` | 1 | `NO_SOLUTION` | `INFEASIBILITY_CERTIFICATE` | +| Optimal within relaxed tolerances | `ALMOST_OPTIMAL` | 1 | `FEASIBLE_POINT` | `FEASIBLE_POINT` | +| Optimal within relaxed tolerances | `ALMOST_OPTIMAL` | 1 | `ALMOST_FEASIBLE_POINT` | `ALMOST_FEASIBLE_POINT` | +| Detected an unbounded ray of the primal | `DUAL_INFEASIBLE` | 1 | `INFEASIBILITY_CERTIFICATE` | `NO_SOLUTION` | +| Stall | `SLOW_PROGRESS` | 1 | * | * | + +### Global branch-and-bound solvers + +Mixed-integer programming solvers fall into this category. + +| What happened? | `TerminationStatus` | `ResultCount` | `PrimalStatus` | `DualStatus` | +| ------------------------------------------------ | ------------------------- | ------------- | ------------------ | ------------- | +| Proved optimality | `OPTIMAL` | 1 | `FEASIBLE_POINT` | `NO_SOLUTION` | +| Presolve detected infeasibility or unboundedness | `INFEASIBLE_OR_UNBOUNDED` | 0 | `NO_SOLUTION` | `NO_SOLUTION` | +| Proved infeasibility | `INFEASIBLE` | 0 | `NO_SOLUTION` | `NO_SOLUTION` | +| Timed out (no solution) | `TIME_LIMIT` | 0 | `NO_SOLUTION` | `NO_SOLUTION` | +| Timed out (with a solution) | `TIME_LIMIT` | 1 | `FEASIBLE_POINT` | `NO_SOLUTION` | +| `CPXMIP_OPTIMAL_INFEAS` | `ALMOST_OPTIMAL` | 1 | `INFEASIBLE_POINT` | `NO_SOLUTION` | + +!!! info + [`CPXMIP_OPTIMAL_INFEAS`](https://www.ibm.com/support/knowledgecenter/en/SSSA5P_12.6.1/ilog.odms.cplex.help/refcallablelibrary/macros/CPXMIP_OPTIMAL_INFEAS.html) + is a CPLEX status that indicates that a preprocessed problem was solved to + optimality, but the solver was unable to recover a feasible solution to the + original problem. Handling this status was one of the motivating drivers + behind the design of MOI. + +### Local search solvers + +Nonlinear programming solvers fall into this category. It also includes +non-global tree search solvers like +[Juniper](https://github.com/lanl-ansi/Juniper.jl). + +| What happened? | `TerminationStatus` | `ResultCount` | `PrimalStatus` | `DualStatus` | +| ------------------------------------------------------ | --------------------------------- | --------------- | ------------------ | ---------------- | +| Converged to a stationary point | `LOCALLY_SOLVED` | 1 | `FEASIBLE_POINT` | `FEASIBLE_POINT` | +| Completed a non-global tree search (with a solution) | `LOCALLY_SOLVED` | 1 | `FEASIBLE_POINT` | `FEASIBLE_POINT` | +| Converged to an infeasible point | `LOCALLY_INFEASIBLE` | 1 | `INFEASIBLE_POINT` | * | +| Completed a non-global tree search (no solution found) | `LOCALLY_INFEASIBLE` | 0 | `NO_SOLUTION` | `NO_SOLUTION` | +| Iteration limit | `ITERATION_LIMIT` | 1 | * | * | +| Diverging iterates | `NORM_LIMIT` or `OBJECTIVE_LIMIT` | 1 | * | * | diff --git a/docs/src/reference/reference.md b/docs/src/reference/reference.md index 6b161019e4..8909b730e4 100644 --- a/docs/src/reference/reference.md +++ b/docs/src/reference/reference.md @@ -250,7 +250,7 @@ VariablePrimalStart VariablePrimal ``` -### Constraints +### [Constraints](@id constraints_ref) Functions for adding and modifying constraints.